From 10eefc0babc9934838f4e8db12430ca706373f57 Mon Sep 17 00:00:00 2001 From: Apache Date: Mon, 15 Jul 2024 02:21:21 -0500 Subject: [PATCH] Init --- .forgejo/workflows/rust.yaml | 24 + .gitignore | 1 + Cargo.lock | 7 + Cargo.toml | 7 + README.md | 57 +++ assets/holy_lisp.svg | 65 +++ src/executor.rs | 862 +++++++++++++++++++++++++++++++++++ src/lib.rs | 8 + src/main.rs | 23 + src/parser.rs | 379 +++++++++++++++ src/test.lisp | 13 + src/tokenizer.rs | 309 +++++++++++++ 12 files changed, 1755 insertions(+) create mode 100644 .forgejo/workflows/rust.yaml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 assets/holy_lisp.svg create mode 100644 src/executor.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/parser.rs create mode 100644 src/test.lisp create mode 100644 src/tokenizer.rs diff --git a/.forgejo/workflows/rust.yaml b/.forgejo/workflows/rust.yaml new file mode 100644 index 0000000..842e94f --- /dev/null +++ b/.forgejo/workflows/rust.yaml @@ -0,0 +1,24 @@ +name: Rust + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + + runs-on: docker + + steps: + - uses: actions/checkout@v4 + - name: Setup + run: DEBIAN_FRONTEND=noninteractive apt-get -yq install cargo + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c41cc9e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..73073dc --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "holy_lisp" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f3bd6c0 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "holy_lisp" +version = "0.1.0" +edition = "2021" +authors = ["Apache "] + +[dependencies] diff --git a/README.md b/README.md new file mode 100644 index 0000000..eb0c862 --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# Holy Lisp + +Holy Lisp Logo + +An opinionated lisp implementation developed in the most intuitive way I could come up with. + +## Features + +### Lists with a function as the **first element** are evaluated when needed + +```lisp +(print (add 1 2)) ; => 3 +(print (1 2 3)) ; => (1 2 3) +``` + +### Quoted lists are **not evaluated** + +```lisp +(print '(add 1 2)) ; => '( 1 2) +``` + +### Elements within quoted lists can be unquoted + +```lisp +(print + '(add + 1 ,(sub 3 1) + ) +) ; => '( 1 2) +``` + +### Maps can be indexed with . + +```lisp +; In this example, ball_pos is a map +(print ball_pos.x ball_pos.y) + +; This is just syntax sugar for +(get ball_pos "x") +``` + +### Functions can be defined and called like so + +```lisp +(fn hello(name) ( + (print (concat "Hello, " name "!")) +)) + +(hello "Joe") ; => "Hello, Joe!" + + +; Anonymous function creation +( + (fn(x) (print x)) + 15 +) ; => 15 +``` \ No newline at end of file diff --git a/assets/holy_lisp.svg b/assets/holy_lisp.svg new file mode 100644 index 0000000..1b98051 --- /dev/null +++ b/assets/holy_lisp.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + diff --git a/src/executor.rs b/src/executor.rs new file mode 100644 index 0000000..9dd5ba6 --- /dev/null +++ b/src/executor.rs @@ -0,0 +1,862 @@ +use std::{collections::HashMap, fmt, time::Instant}; + +use crate::PreLispValue; + +#[cfg(test)] +mod tests { + use crate::{LispState, LispValue, Parser, Tokenizer}; + + use super::add_function; + + fn get_exec_result(source: &str) -> Result, Box> { + let mut state = LispState::new(); + + state + .table + .insert(String::from("output"), LispValue::List(vec![], false)); + add_function(&mut state.table, "print", |st, args| { + if let LispValue::List(v, _) = st.table.get_mut("output").unwrap() { + v.push(LispValue::List(args, false)) + } + + Ok(vec![LispValue::Nil]) + }); + + let mut tokenizer = Tokenizer::new(String::from(source)); + let mut parser = Parser::new(tokenizer.tokenize()?); + let instructions = parser.parse(); + + state.execute(instructions)?; + + let mut out = Vec::new(); + if let LispValue::List(outerlist, _) = &state.table["output"] { + for list in outerlist { + if let LispValue::List(innerlist, _) = list { + for innervalue in innerlist { + out.push(innervalue.to_owned()); + } + } + } + } + + Ok(out) + } + + #[test] + fn test_hello_world() -> Result<(), Box> { + assert_eq!( + vec![LispValue::String(String::from("Hello, World!"))], + get_exec_result( + " + (print \"Hello, World!\") + " + )? + ); + Ok(()) + } + + #[test] + fn test_math() -> Result<(), Box> { + assert_eq!( + vec![LispValue::Float(16.05)], + get_exec_result( + " + (print + (add + (div + 4.5 + (sub 0.5 0.2) + ) + (mul 21 0.05) + ) + ) + " + )? + ); + Ok(()) + } +} + +pub struct LispState { + table: HashMap, +} + +#[derive(Debug)] +pub struct RuntimeError { + message: String, +} + +impl fmt::Display for RuntimeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Runtime Error: {}", self.message) + } +} + +impl std::error::Error for RuntimeError {} + +type RustFunctionType = fn(&mut LispState, Vec) -> Result, RuntimeError>; + +#[derive(Debug, Clone, PartialEq)] +pub enum LispValue { + Nil, + True, + False, + String(String), + Integer(i32), + Float(f32), + List(Vec, bool), + Instant(Instant), + LispFunction(String, Vec, Vec), + RustFunction(String, RustFunctionType), + Map(HashMap), + Var(String), +} + +impl fmt::Display for LispValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + #[allow(unreachable_patterns)] + match self { + LispValue::Nil => write!(f, "nil"), + LispValue::True => write!(f, "true"), + LispValue::False => write!(f, "false"), + LispValue::String(str) => write!(f, "\"{}\"", str), + LispValue::LispFunction(name, _, _) => write!(f, "", name), + LispValue::RustFunction(name, _) => write!(f, "", name), + LispValue::Integer(num) => write!(f, "{}", num), + LispValue::Float(num) => write!(f, "{}", num), + LispValue::List(elems, quoted) => { + let mut strs = Vec::new(); + + for e in elems { + strs.push(e.to_string()); + } + + if *quoted { + write!(f, "'({})", strs.join(" ")) + } else { + write!(f, "({})", strs.join(" ")) + } + } + val => { + write!(f, "{}", format!("{:?}", val)) + } + } + } +} + +fn lisp_display(v: LispValue) -> String { + #[allow(unreachable_patterns)] + match v { + LispValue::Nil => String::from("nil"), + LispValue::True => String::from("true"), + LispValue::False => String::from("false"), + LispValue::String(str) => str, + LispValue::LispFunction(name, _, _) => format!("", name), + LispValue::RustFunction(name, _) => format!("", name), + LispValue::Integer(num) => num.to_string(), + LispValue::Float(num) => num.to_string(), + LispValue::List(elems, quoted) => { + let mut strs = Vec::new(); + + for e in elems { + strs.push(e.to_string()); + } + + if quoted { + format!("'({})", strs.join(" ")) + } else { + format!("({})", strs.join(" ")) + } + } + val => { + format!("{}", val) + } + } +} + +fn get_type(v: &LispValue) -> String { + match v { + LispValue::Nil => String::from("nil"), + LispValue::True => String::from("bool"), + LispValue::False => String::from("bool"), + LispValue::Integer(_) => String::from("int"), + LispValue::Float(_) => String::from("float"), + LispValue::String(_) => String::from("string"), + LispValue::List(_, _) => String::from("list"), + LispValue::Instant(_) => String::from("instant"), + LispValue::LispFunction(_, _, _) => String::from("function"), + LispValue::RustFunction(_, _) => String::from("function"), + LispValue::Map(_) => String::from("map"), + LispValue::Var(_) => String::from("symbol"), + } +} + +fn is_falsy(v: &LispValue) -> bool { + match v { + LispValue::Nil => true, + LispValue::False => true, + _ => false, + } +} + +fn is_truthy(v: &LispValue) -> bool { + !is_falsy(v) +} + +impl From<&str> for LispValue { + fn from(value: &str) -> Self { + LispValue::String(value.to_string()) + } +} +impl From for LispValue { + fn from(value: String) -> Self { + LispValue::String(value) + } +} +impl From for LispValue { + fn from(value: i32) -> Self { + LispValue::Integer(value) + } +} +impl From for LispValue { + fn from(value: f32) -> Self { + LispValue::Float(value) + } +} +impl From for LispValue { + fn from(value: bool) -> Self { + match value { + true => LispValue::True, + false => LispValue::False, + } + } +} +impl From> for LispValue { + fn from(value: Vec) -> Self { + LispValue::List(value, false) + } +} + +fn add_function + Clone>( + table: &mut HashMap, + name: T, + func: RustFunctionType, +) { + table.insert( + name.clone().into(), + LispValue::RustFunction(name.into(), func), + ); +} + +impl LispState { + pub fn new() -> LispState { + let mut table = HashMap::new(); + + add_function(&mut table, "now", |_, _| { + Ok(vec![LispValue::Instant(Instant::now())]) + }); + + add_function(&mut table, "since", |_, args| { + if args.len() != 1 { + return Err(RuntimeError { + message: format!("Bad arguments to since (expected 1, got {})", args.len()), + }); + } + if let LispValue::Instant(i) = args[0] { + Ok(vec![LispValue::Float( + Instant::now().duration_since(i).as_secs_f32(), + )]) + } else { + Err(RuntimeError { + message: format!( + "Bad arguments to since (expected instant, got {})", + get_type(&args[0]) + ), + }) + } + }); + + add_function(&mut table, "type", |_, args| { + if args.len() != 1 { + return Err(RuntimeError { + message: format!("Bad arguments to type (expected 1, got {})", args.len()), + }); + } + Ok(vec![LispValue::String(get_type(&args[0]))]) + }); + + add_function(&mut table, "sleep", |_, args| { + if args.len() != 1 { + return Err(RuntimeError { + message: format!("Bad arguments to sleep (expected 1, got {})", args.len()), + }); + } + + if let LispValue::Float(n) = args[0] { + std::thread::sleep(std::time::Duration::from_secs_f32(n)); + } else if let LispValue::Integer(n) = args[0] { + std::thread::sleep(std::time::Duration::from_secs(n as u64)); + } else { + return Err(RuntimeError { + message: format!( + "Bad arguments to sleep (expected float or int, got {})", + get_type(&args[0]) + ), + }); + } + + Ok(vec![LispValue::Nil]) + }); + + add_function(&mut table, "or", |_, args| { + if args.len() != 2 { + return Err(RuntimeError { + message: format!("Bad arguments to or (expected 2, got {})", args.len()), + }); + } + if is_truthy(&args[0]) { + Ok(vec![args[0].clone()]) + } else { + Ok(vec![args[1].clone()]) + } + }); + + add_function(&mut table, "and", |_, args| { + if args.len() != 2 { + return Err(RuntimeError { + message: format!("Bad arguments to and (expected 2, got {})", args.len()), + }); + } + if is_truthy(&args[0]) { + Ok(vec![args[1].clone()]) + } else { + Ok(vec![args[0].clone()]) + } + }); + + add_function(&mut table, "echo", |_, args| { + return Ok(args); + }); + + add_function(&mut table, "set", |state, args| { + if args.len() != 2 { + return Err(RuntimeError { + message: format!("Bad arguments to set (expected 2, got {})", args.len()), + }); + } + + if let LispValue::String(name) = &args[0] { + state.table.insert(name.to_owned(), args[1].clone()); + } else { + return Err(RuntimeError { + message: format!( + "Bad argument 1 to set (expected string, got {})", + get_type(&args[0]) + ), + }); + } + + Ok(vec![args[1].clone()]) + }); + + add_function(&mut table, "get", |_, args| { + if args.len() != 2 { + return Err(RuntimeError { + message: format!("Bad arguments to get (expected 2, got {})", args.len()), + }); + } + + if let LispValue::Map(map) = &args[0] { + if let LispValue::String(str) = &args[1] { + return Ok(vec![map.get(str).unwrap_or(&LispValue::Nil).to_owned()]); + } else { + return Err(RuntimeError { + message: format!( + "Bad argument 2 to get (expected string, got {})", + get_type(&args[0]) + ), + }); + } + } else { + return Err(RuntimeError { + message: format!( + "Bad argument 1 to get (expected map, got {})", + get_type(&args[0]) + ), + }); + } + }); + + add_function(&mut table, "print", |_, args| { + let mut strings = Vec::new(); + for val in args { + strings.push(lisp_display(val)); + } + + let str = strings.join(" "); + println!("{}", str); + + Ok(vec![LispValue::Nil]) + }); + + add_function(&mut table, "concat", |_, args| { + let mut strings = Vec::new(); + for val in args { + strings.push(lisp_display(val)); + } + + let str = strings.concat(); + Ok(vec![LispValue::String(str)]) + }); + + add_function(&mut table, "add", |_, args| { + if args.len() < 1 { + return Ok(vec![LispValue::Nil]); + } else if args.len() == 1 { + return Ok(vec![args[0].clone()]); + } + + let mut t = "int"; + for v in &args { + match v { + LispValue::Float(_) => t = "float", + LispValue::Integer(_) => (), + _ => { + return Err(RuntimeError { + message: format!( + "Invalid argument to 'add' (expected float or int, got {}", + get_type(v) + ), + }) + } + } + } + + match t { + "int" => { + let mut sum = 0; + for v in &args { + if let LispValue::Integer(n) = v { + sum += n; + } else { + unreachable!() + } + } + return Ok(vec![LispValue::Integer(sum)]); + } + "float" => { + let mut sum = 0.0 as f32; + for v in &args { + if let LispValue::Float(n) = v { + sum += n; + } else if let LispValue::Integer(n) = v { + sum += *n as f32; + } else { + unreachable!() + } + } + return Ok(vec![LispValue::Float(sum)]); + } + _ => unreachable!(), + } + }); + + add_function(&mut table, "sub", |_, args| { + if args.len() < 1 { + return Ok(vec![LispValue::Nil]); + } else if args.len() == 1 { + return Ok(vec![args[0].clone()]); + } + + let mut t = "int"; + for v in &args { + match v { + LispValue::Float(_) => t = "float", + LispValue::Integer(_) => (), + _ => { + return Err(RuntimeError { + message: format!( + "Invalid argument to 'sub' (expected float or int, got {}", + get_type(v) + ), + }) + } + } + } + + match t { + "int" => { + if let LispValue::Integer(n) = args[0] { + let mut sum = n; + for i in 1..args.len() { + if let LispValue::Integer(n) = &args[i] { + sum -= n; + } else { + unreachable!() + } + } + return Ok(vec![LispValue::Integer(sum)]); + } else { + unreachable!() + } + } + "float" => { + if let LispValue::Float(n) = args[0] { + let mut sum = n; + for i in 1..args.len() { + if let LispValue::Float(n) = &args[i] { + sum -= *n; + } else if let LispValue::Integer(n) = &args[i] { + sum -= *n as f32; + } else { + unreachable!() + } + } + return Ok(vec![LispValue::Float(sum)]); + } else if let LispValue::Integer(n) = args[0] { + let mut sum = n as f32; + for i in 1..args.len() { + if let LispValue::Float(n) = &args[i] { + sum -= *n; + } else if let LispValue::Integer(n) = &args[i] { + sum -= *n as f32; + } else { + unreachable!() + } + } + return Ok(vec![LispValue::Float(sum)]); + } + { + unreachable!() + } + } + _ => unreachable!(), + } + }); + + add_function(&mut table, "mul", |_, args| { + if args.len() < 1 { + return Ok(vec![LispValue::Nil]); + } else if args.len() == 1 { + return Ok(vec![args[0].clone()]); + } + + let mut t = "int"; + for v in &args { + match v { + LispValue::Float(_) => t = "float", + LispValue::Integer(_) => (), + _ => { + return Err(RuntimeError { + message: format!( + "Invalid argument to 'mul' (expected float or int, got {}", + get_type(v) + ), + }) + } + } + } + + match t { + "int" => { + if let LispValue::Integer(n) = args[0] { + let mut sum = n; + for i in 1..args.len() { + if let LispValue::Integer(n) = &args[i] { + sum *= n; + } else { + unreachable!() + } + } + return Ok(vec![LispValue::Integer(sum)]); + } else { + unreachable!() + } + } + "float" => { + if let LispValue::Float(n) = args[0] { + let mut product = n; + for i in 1..args.len() { + if let LispValue::Float(n) = &args[i] { + product *= *n; + } else if let LispValue::Integer(n) = &args[i] { + product *= *n as f32; + } else { + unreachable!() + } + } + return Ok(vec![LispValue::Float(product)]); + } else if let LispValue::Integer(n) = args[0] { + let mut product = n as f32; + for i in 1..args.len() { + if let LispValue::Float(n) = &args[i] { + product *= *n; + } else if let LispValue::Integer(n) = &args[i] { + product *= *n as f32; + } else { + unreachable!() + } + } + return Ok(vec![LispValue::Float(product)]); + } + { + unreachable!() + } + } + _ => unreachable!(), + } + }); + + add_function(&mut table, "div", |_, args| { + if args.len() < 1 { + return Ok(vec![LispValue::Nil]); + } else if args.len() == 1 { + return Ok(vec![args[0].clone()]); + } + + let mut t = "int"; + for v in &args { + match v { + LispValue::Float(_) => t = "float", + LispValue::Integer(_) => (), + _ => { + return Err(RuntimeError { + message: format!( + "Invalid argument to 'div' (expected float or int, got {}", + get_type(v) + ), + }) + } + } + } + + match t { + "int" => { + if let LispValue::Integer(n) = args[0] { + let mut sum = n; + for i in 1..args.len() { + if let LispValue::Integer(n) = &args[i] { + sum /= n; + } else { + unreachable!() + } + } + return Ok(vec![LispValue::Integer(sum)]); + } else { + unreachable!() + } + } + "float" => { + if let LispValue::Float(n) = args[0] { + let mut product = n; + for i in 1..args.len() { + if let LispValue::Float(n) = &args[i] { + product /= *n; + } else if let LispValue::Integer(n) = &args[i] { + product /= *n as f32; + } else { + unreachable!() + } + } + return Ok(vec![LispValue::Float(product)]); + } else if let LispValue::Integer(n) = args[0] { + let mut product = n as f32; + for i in 1..args.len() { + if let LispValue::Float(n) = &args[i] { + product /= *n; + } else if let LispValue::Integer(n) = &args[i] { + product /= *n as f32; + } else { + unreachable!() + } + } + return Ok(vec![LispValue::Float(product)]); + } + { + unreachable!() + } + } + _ => unreachable!(), + } + }); + + LispState { table } + } + + fn handle_prevalues(&self, input: Vec) -> Vec { + let mut out = Vec::new(); + + for v in input { + out.push(match v { + PreLispValue::Nil => LispValue::Nil, + PreLispValue::True => LispValue::True, + PreLispValue::False => LispValue::False, + PreLispValue::String(str) => LispValue::String(str), + PreLispValue::Integer(num) => LispValue::Integer(num), + PreLispValue::Float(num) => LispValue::Float(num), + PreLispValue::List(list, quoted) => { + LispValue::List(Self::handle_prevalues(self, list), quoted) + } + PreLispValue::LispFunction(name, arg_names, instructions) => { + LispValue::LispFunction( + name, + arg_names, + Self::handle_prevalues(self, instructions), + ) + } + PreLispValue::Var(name) => LispValue::Var(name), + }) + } + + out + } + + fn eval_list( + &mut self, + env: &mut HashMap, + list: &Vec, + quoted: bool, + ) -> Result<(Vec, bool), RuntimeError> { + if list.len() < 1 { + return Ok((vec![LispValue::List(Vec::new(), quoted)], false)); + } + + let list = &mut list.clone(); + + for i in 0..list.len() { + if let LispValue::Var(name) = &list[i] { + list[i] = env + .get(name) + .unwrap_or_else(|| self.table.get(name).unwrap_or(&LispValue::Nil)) + .to_owned(); + } + if let LispValue::List(elements, quoted) = &list[i] { + let (elems, astuple) = self.eval_list(env, elements, *quoted)?; + if astuple { + list[i] = elems.get(0).unwrap_or(&LispValue::Nil).to_owned(); + for j in 1..elems.len() { + list.insert(i, elems[j].to_owned()); + } + } else { + list[i] = LispValue::List(elems, *quoted); + } + } + } + + if quoted { + return Ok((list.to_vec(), false)); + } + + if let LispValue::RustFunction(_name, f) = &list[0] { + Ok((f(self, list[1..].to_vec())?, true)) + } else if let LispValue::LispFunction(_name, _, _) = &list[0] { + Ok(( + self.call_lisp_func_internal(env, &list[0], list[1..].to_vec())?, + true, + )) + } else { + Ok((list.to_vec(), false)) + } + } + + fn call_lisp_func_internal( + &mut self, + env: &mut HashMap, + f: &LispValue, + args: Vec, + ) -> Result, RuntimeError> { + let (arg_names, instructions) = match f { + LispValue::LispFunction(_, arg_names, i) => (arg_names, i), + _ => { + return Err(RuntimeError { + message: format!( + "Invalid argument to call_lisp_func_internal (expected function, got {})", + get_type(f) + ), + }) + } + }; + + for i in 0..arg_names.len() { + env.insert( + arg_names[i].to_owned(), + args.get(i).unwrap_or(&LispValue::Nil).to_owned(), + ); + } + + let mut out = Vec::new(); + for l in instructions { + if let LispValue::List(elements, quoted) = l { + let (values, astuple) = self.eval_list(env, &elements, *quoted)?; + if values.len() > 0 && values[0] != LispValue::Nil { + if astuple { + for v in values { + out.push(v); + } + } else { + out.push(values.into()); + } + } + } + } + Ok(out) + } + + pub fn call_lisp_func( + &mut self, + f: &LispValue, + args: Vec>, + ) -> Result, RuntimeError> { + if !matches!(f, LispValue::LispFunction(_, _, _)) { + return Err(RuntimeError { + message: format!( + "Invalid argument to call_lisp_func_internal (expected function, got {})", + get_type(&f) + ), + }); + } + + let mut vargs = Vec::new(); + for v in args { + vargs.push(v.into()); + } + + self.call_lisp_func_internal(&mut HashMap::new(), &f, vargs) + } + + pub fn get_func(&mut self, name: impl Into) -> Option { + if let Some(v) = self.table.get(&name.into()) { + Some(v.to_owned()) + } else { + None + } + } + + pub fn execute( + &mut self, + instructions: Vec, + ) -> Result, RuntimeError> { + let instructions = Self::handle_prevalues(self, instructions); + + let mut outs = Vec::new(); + + let mut env = HashMap::new(); + + for val in instructions { + if let LispValue::List(elements, quoted) = val { + let (vals, astuple) = self.eval_list(&mut env, &elements, quoted)?; + if astuple { + for val in vals { + outs.push(val); + } + } else { + outs.push(vals.into()) + } + } else { + outs.push(val); + } + } + + Ok(outs) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..da22c5a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,8 @@ +mod tokenizer; +pub use tokenizer::*; + +mod parser; +pub use parser::*; + +mod executor; +pub use executor::*; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..5430b3a --- /dev/null +++ b/src/main.rs @@ -0,0 +1,23 @@ +mod tokenizer; +use tokenizer::*; + +mod parser; +use parser::*; + +mod executor; +use executor::*; + +fn main() -> Result<(), Box> { + let source = std::fs::read_to_string("src/test.lisp").unwrap(); + + let mut tokenizer = Tokenizer::new(source); + let tokens = tokenizer.tokenize()?; + let mut parser = Parser::new(tokens); + let instructions = parser.parse(); + + let mut state = LispState::new(); + + state.execute(instructions)?; + + Ok(()) +} diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..382fe23 --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,379 @@ +use std::{collections::HashMap, fmt}; + +#[cfg(test)] +mod tests { + use crate::{Parser, PreLispValue, Tokenizer, TokenizerError}; + + fn quick_parse(source: &str) -> Result, TokenizerError> { + let mut tokenizer = Tokenizer::new(String::from(source)); + let tokens = tokenizer.tokenize()?; + let mut parser = Parser::new(tokens); + Ok(parser.parse()) + } + + fn var(name: &str) -> PreLispValue { + PreLispValue::Var(String::from(name)) + } + fn string(s: &str) -> PreLispValue { + PreLispValue::String(String::from(s)) + } + fn list(v: Vec, quoted: bool) -> PreLispValue { + PreLispValue::List(v, quoted) + } + fn int(n: i32) -> PreLispValue { + PreLispValue::Integer(n) + } + fn float(n: f32) -> PreLispValue { + PreLispValue::Float(n) + } + + #[test] + fn test_hello_world() -> Result<(), TokenizerError> { + assert_eq!( + vec![list(vec![var("print"), string("Hello, World!")], false)], + quick_parse( + " + (print \"Hello, World!\") + " + )? + ); + Ok(()) + } + + #[test] + fn test_math() -> Result<(), TokenizerError> { + assert_eq!( + vec![list( + vec![ + var("print"), + list( + vec![ + var("add"), + list( + vec![ + var("div"), + float(4.5), + list(vec![var("sub"), float(0.5), float(0.2)], false) + ], + false + ), + list(vec![var("mul"), int(21), float(0.05)], false) + ], + false + ) + ], + false + )], + quick_parse( + " + (print + (add + (div + 4.5 + (sub 0.5 0.2) + ) + (mul 21 0.05) + ) + ) + " + )? + ); + Ok(()) + } +} + +use crate::Token; + +#[derive(Debug, Clone, PartialEq)] +pub enum PreLispValue { + Nil, + True, + False, + String(String), + Var(String), + Integer(i32), + Float(f32), + List(Vec, bool), // Values, Quoted + LispFunction(String, Vec, Vec), +} + +impl fmt::Display for PreLispValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PreLispValue::Nil => write!(f, "nil"), + PreLispValue::True => write!(f, "true"), + PreLispValue::False => write!(f, "false"), + PreLispValue::String(str) => write!(f, "{}", str), + PreLispValue::LispFunction(name, _, _) => write!(f, "", name), + PreLispValue::Integer(num) => write!(f, "{}", num), + PreLispValue::Float(num) => write!(f, "{}", num), + _ => todo!(), + } + } +} + +impl From<&str> for PreLispValue { + fn from(value: &str) -> Self { + PreLispValue::String(value.to_string()) + } +} +impl From for PreLispValue { + fn from(value: String) -> Self { + PreLispValue::String(value) + } +} +impl From for PreLispValue { + fn from(value: i32) -> Self { + PreLispValue::Integer(value) + } +} +impl From for PreLispValue { + fn from(value: f32) -> Self { + PreLispValue::Float(value) + } +} +impl From> for PreLispValue { + fn from(value: Vec) -> Self { + PreLispValue::List(value, false) + } +} + +// TODO: Handle failure +fn read_exp(open_paren_idx: usize, tokens: &Vec) -> Option<(Vec, usize)> /* Option */ +{ + let mut tkns = Vec::new(); + let mut depth = 0; + + for i in open_paren_idx..tokens.len() { + match &tokens[i] { + Token::OpenParen => { + if depth != 0 { + tkns.push(Token::OpenParen); + } + depth += 1; + } + Token::CloseParen => { + depth -= 1; + if depth == 0 { + return Some((tkns, i)); + } else { + tkns.push(Token::CloseParen) + } + } + token => { + tkns.push(token.clone()); + } + } + } + + None +} + +fn parse_exp(tokens: Vec, quoted: bool) -> PreLispValue { + let mut values = Vec::new(); + + if tokens.len() < 1 { + return PreLispValue::List(values, false); + } + + let mut quote_next_list = false; + let mut unquote_next_list = false; + + let mut i = 0; + while i < tokens.len() { + let tkn = &tokens[i]; + match tkn { + Token::Quote => { + quote_next_list = true; + } + Token::Unquote => { + unquote_next_list = true; + } + Token::OpenParen => { + let (tkns, close_paren_idx) = read_exp(i, &tokens).unwrap(); + values.push(parse_exp( + tkns, + if unquote_next_list { + false + } else { + quote_next_list || quoted + }, + )); + i = close_paren_idx; + quote_next_list = false; + unquote_next_list = false; + } + Token::Identifier(name) => match name.as_str() { + "nil" => values.push(PreLispValue::Nil), + "true" => values.push(PreLispValue::True), + "false" => values.push(PreLispValue::False), + _ => values.push(PreLispValue::Var(name.to_owned())), + }, + Token::Index(map, idx) => { + values.push(PreLispValue::List( + vec![ + PreLispValue::Var(String::from("get")), + PreLispValue::Var(map.to_owned()), + PreLispValue::String(idx.to_owned()), + ], + false, + )); + } + Token::Integer(num) => { + values.push(PreLispValue::Integer(*num)); + } + Token::Float(num) => { + values.push(PreLispValue::Float(*num)); + } + Token::String(str) => { + values.push(PreLispValue::String(str.to_owned())); + } + Token::CloseParen => { + panic!("Unexpected closing parenthesis"); + } + } + i += 1; + } + + PreLispValue::List(values, quoted) +} + +pub struct Parser { + tokens: Vec, + macros: HashMap) -> Vec>, +} + +fn add_macro( + macros: &mut HashMap) -> Vec>, + name: impl Into, + mac: fn(Vec) -> Vec, +) { + macros.insert(name.into(), mac); +} + +impl Parser { + pub fn new(tokens: Vec) -> Parser { + let mut macros = HashMap::new(); + + add_macro(&mut macros, "set", |x| { + if x.len() != 3 + || (!matches!(x[1], PreLispValue::Var(_)) + && !matches!(x[1], PreLispValue::String(_))) + { + return x; + } + + let mut list = x.clone(); + + if let PreLispValue::Var(name) = &list[1] { + list[1] = PreLispValue::String(name.to_owned()) + } + + list + }); + + add_macro(&mut macros, "fn", |x| { + if x.len() < 3 || x.len() > 4 { + return x; + } + + let mut name: String = String::new(); + let mut args: &Vec = &Vec::new(); + let mut list: &Vec = &Vec::new(); + + if x.len() == 3 { + if !matches!(x[1], PreLispValue::List(_, _)) + || !matches!(x[2], PreLispValue::List(_, _)) + { + return x; + } + name = String::from("Anonymous"); + if let PreLispValue::List(l, _) = &x[1] { + args = l; + } + if let PreLispValue::List(l, _) = &x[2] { + list = l; + } + } else if x.len() == 4 { + if !matches!(x[1], PreLispValue::Var(_)) + || !matches!(x[2], PreLispValue::List(_, _)) + || !matches!(x[3], PreLispValue::List(_, _)) + { + return x; + } + if let PreLispValue::Var(n) = &x[1] { + name = n.to_owned(); + } + if let PreLispValue::List(l, _) = &x[2] { + args = l; + } + if let PreLispValue::List(l, _) = &x[3] { + list = l; + } + } else { + return x; + } + + let mut arg_names = Vec::new(); + for arg in args { + if let PreLispValue::Var(n) = arg { + arg_names.push(n.to_owned()); + } + } + + let func = PreLispValue::LispFunction(name.clone(), arg_names, list.to_owned()); + + if x.len() == 4 { + return vec![PreLispValue::Var(String::from("set")), name.into(), func]; + } + + return vec![PreLispValue::Var(String::from("echo")), func]; + }); + + Parser { tokens, macros } + } + + fn macro_check(&mut self, input: Vec, quoted: bool) -> Vec { + if quoted || input.len() < 1 { + return input; + } + + let mut output = input.clone(); + + for i in 0..output.len() { + if let PreLispValue::List(elems, quoted) = &output[i] { + output[i] = PreLispValue::List(self.macro_check(elems.to_vec(), *quoted), *quoted); + } + } + + if let PreLispValue::Var(name) = &output[0] { + if self.macros.contains_key(name) { + return self.macros.get(name).unwrap()(output); + } + } + + output + } + + pub fn parse(&mut self) -> Vec { + let mut values = Vec::new(); + + let mut i = 0; + while i < self.tokens.len() { + match &self.tokens[i] { + Token::OpenParen => { + let (tkns, close_paren_idx) = read_exp(i, &self.tokens).unwrap(); + values.push(parse_exp(tkns, false)); + i = close_paren_idx; + } + tkn => { + // TODO: Include more error data + panic!("Unexpected token {:?}", tkn); + } + } + i += 1; + } + + self.macro_check(values, false) + } +} diff --git a/src/test.lisp b/src/test.lisp new file mode 100644 index 0000000..99829da --- /dev/null +++ b/src/test.lisp @@ -0,0 +1,13 @@ +(fn countuntil(until n) ( + (set n (add (or n 0) 1)) + + )) + + +(set start (now)) + +(countuntil 100) + +(print (since start)) + + diff --git a/src/tokenizer.rs b/src/tokenizer.rs new file mode 100644 index 0000000..998645c --- /dev/null +++ b/src/tokenizer.rs @@ -0,0 +1,309 @@ +use std::fmt; + +#[cfg(test)] +mod tests { + use crate::{Token, Tokenizer, TokenizerError}; + + fn quick_tokenize(source: &str) -> Result, TokenizerError> { + let mut tokenizer = Tokenizer::new(String::from(source)); + return tokenizer.tokenize(); + } + + fn open() -> Token { + Token::OpenParen + } + fn close() -> Token { + Token::CloseParen + } + fn iden(s: &str) -> Token { + Token::Identifier(String::from(s)) + } + fn string(s: &str) -> Token { + Token::String(String::from(s)) + } + fn int(n: i32) -> Token { + Token::Integer(n) + } + fn float(n: f32) -> Token { + Token::Float(n) + } + + + #[test] + fn test_hello_world() -> Result<(), TokenizerError> { + assert_eq!( + vec![ + open(), + iden("print"), + string("Hello, World!"), + close() + ], + quick_tokenize(" + (print \"Hello, World!\") + ")? + ); + Ok(()) + } + + #[test] + fn test_math() -> Result<(), TokenizerError> { + assert_eq!( + vec![ + open(), + iden("print"), + open(), + iden("add"), + open(), + iden("div"), + float(4.5), + open(), + iden("sub"), + float(0.5), + float(0.2), + close(), + close(), + open(), + iden("mul"), + int(21), + float(0.05), + close(), + close(), + close() + ], + quick_tokenize(" + (print + (add + (div + 4.5 + (sub 0.5 0.2) + ) + (mul 21 0.05) + ) + ) + ")? + ); + Ok(()) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Token { + OpenParen, + CloseParen, + Quote, + Unquote, + Identifier(String), + Index(String, String), + String(String), + Integer(i32), + Float(f32) +} + +#[derive(Debug)] +pub struct TokenizerError { + line: u64, + column: u64, + + line_end: u64, + column_end: u64, + + message: String, +} + +impl fmt::Display for TokenizerError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.line != self.line_end || self.column != self.column_end { + return write!(f, "Error from {}:{} to {}:{}, '{}'", self.line, self.column, self.line_end, self.column_end, self.message); + } + write!(f, "Error at {}:{}, '{}'", self.line, self.column, self.message) + } +} + +impl std::error::Error for TokenizerError {} + +pub struct Tokenizer { + code: String, + line: u64, + column: u64, + + reading_string: bool, + escape_next_char: bool, + + reading_num: bool, + is_float: bool, + + reading_identifier: bool, + is_index: bool, + indexing: Option, + + skipping_comment: bool, + + storage: Vec, +} + +impl Tokenizer { + pub fn new(code: String) -> Tokenizer { + Self { + code, + line: 1, + column: 1, + + reading_num: false, + is_float: false, + + reading_string: false, + escape_next_char: false, + + reading_identifier: false, + is_index: false, + indexing: None, + + skipping_comment: false, + + storage: Vec::new() + } + } + + pub fn tokenize(&mut self) -> Result, TokenizerError> { + let mut tokens = Vec::new(); + + for char in self.code.chars() { + let line = self.line; + let column = self.column; + + self.column += 1; + if char == '\n' { + self.line += 1; + self.column = 1; + } + + if self.skipping_comment { + match char { + '\n' => { + self.skipping_comment = false; + }, + _ => continue + } + } + + if self.reading_identifier { + if char.is_alphabetic() { + self.storage.push(char); + continue; + } else if char == '.' { + self.is_index = true; + self.indexing = Some(self.storage.iter().collect()); + self.storage.clear(); + continue; + } + + self.reading_identifier = false; + + if self.is_index { + self.is_index = false; + + tokens.push(Token::Index(self.indexing.as_ref().unwrap().to_string(), self.storage.iter().collect())); + + self.storage.clear(); + self.indexing = None; + } else { + tokens.push(Token::Identifier(self.storage.iter().collect())); + } + + self.storage.clear(); + } + + if self.reading_num { + // Allow spacing numbers like 1_000_000 + if !char.is_numeric() && char != '_' && char != '.' { + self.reading_num = false; + + if self.is_float { + tokens.push(Token::Float(self.storage.iter().collect::().parse().unwrap())); + } else { + tokens.push(Token::Integer(self.storage.iter().collect::().parse().unwrap())); + } + + self.is_float = false; + + self.storage.clear(); + } else { + if char == '.' { + self.is_float = true; + } + if char != '_' { + self.storage.push(char); + } + continue; + } + } + + if self.reading_string { + if char == '"' && !self.escape_next_char { + self.reading_string = false; + + tokens.push(Token::String(self.storage.iter().collect())); + self.storage.clear(); + continue; + } + + if char == '\n' { + return Err(TokenizerError { + line: line - self.storage.len() as u64 - 1, + column, + + line_end: line, + column_end: column, + + message: String::from("Expected \", got \\n"), + }) + } + + if char == '\\' { + self.escape_next_char = true; + continue; + } + + self.storage.push(char); + + continue; + } + + match char { + ';' => self.skipping_comment = true, + '(' => tokens.push(Token::OpenParen), + ')' => tokens.push(Token::CloseParen), + '"' => { + self.reading_string = true; + self.storage.clear(); + }, + '\'' => tokens.push(Token::Quote), + ',' => tokens.push(Token::Unquote), + c => { + if self.reading_identifier || self.reading_num || self.reading_string { + continue; + } + if c.is_alphabetic() { + self.reading_identifier = true; + self.storage.clear(); + self.storage.push(c); + continue; + } else if c.is_numeric() || c == '.' { + self.reading_num = true; + self.storage.clear(); + if c.is_numeric() { + self.storage.push(c); + } else if c == '.' { + self.is_float = true; + self.storage.push('0'); + self.storage.push(c); + } + continue; + } + } + } + } + + Ok(tokens) + } +} \ No newline at end of file