Basic read and print are working perfectly
This commit is contained in:
parent
25a451e23e
commit
f7f90cd78f
4 changed files with 238 additions and 52 deletions
49
src/main.rs
49
src/main.rs
|
@ -1,7 +1,10 @@
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
|
use printer::print;
|
||||||
|
use reader::read;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rustyline::{config::Configurer, error::ReadlineError};
|
use rustyline::{config::Configurer, error::ReadlineError};
|
||||||
|
|
||||||
|
pub mod printer;
|
||||||
pub mod reader;
|
pub mod reader;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
use types::*;
|
use types::*;
|
||||||
|
@ -15,19 +18,19 @@ fn main() -> Result<()> {
|
||||||
match entry {
|
match entry {
|
||||||
Ok(line) => {
|
Ok(line) => {
|
||||||
rl.add_history_entry(line.as_str())?;
|
rl.add_history_entry(line.as_str())?;
|
||||||
eval(line.as_str().into(), env.clone())?;
|
eval(line.as_str(), env.clone())?;
|
||||||
},
|
}
|
||||||
Err(ReadlineError::Interrupted) => {
|
Err(ReadlineError::Interrupted) => {
|
||||||
println!("CTRL-C");
|
println!("CTRL-C");
|
||||||
break
|
break;
|
||||||
},
|
}
|
||||||
Err(ReadlineError::Eof) => {
|
Err(ReadlineError::Eof) => {
|
||||||
println!("CTRL-D");
|
println!("CTRL-D");
|
||||||
break
|
break;
|
||||||
},
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
println!("Error: {:?}", err);
|
println!("Error: {:?}", err);
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,20 +49,22 @@ fn read_eval_print(str: &str) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval(expr: Expression, env: Environment) -> Result<()> {
|
fn eval(expr: &str, env: Environment) -> Result<()> {
|
||||||
let regex = Regex::new(r#"^\(([[:ascii:]] .*)\)$"#).unwrap();
|
let ast = read(expr);
|
||||||
let exp = regex.captures_iter(&expr.expr);
|
let expr = print(&ast);
|
||||||
for expr in exp {
|
// let regex = Regex::new(r"^\(([[:ascii:]] .*)\)$").unwrap();
|
||||||
// let regex = Regex::new(r#"^(\w)$"#).unwrap();
|
// let exp = regex.captures_iter(&expr.expr);
|
||||||
// let mut atom = regex.captures_iter(atom?.get(0).unwrap().as_str());
|
// for expr in exp {
|
||||||
// for atom in atom {
|
// // let regex = Regex::new(r#"^(\w)$"#).unwrap();
|
||||||
// }
|
// // let mut atom = regex.captures_iter(atom?.get(0).unwrap().as_str());
|
||||||
for atom in expr.iter() {
|
// // for atom in atom {
|
||||||
if let Some(atom) = atom {
|
// // }
|
||||||
println!("{:?}", atom);
|
// for atom in expr.iter() {
|
||||||
};
|
// if let Some(atom) = atom {
|
||||||
}
|
// println!("{:?}", atom);
|
||||||
}
|
// };
|
||||||
println!("{}", expr.expr);
|
// }
|
||||||
|
// }
|
||||||
|
println!("{}", expr);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
52
src/printer.rs
Normal file
52
src/printer.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
use crate::Value;
|
||||||
|
|
||||||
|
pub fn print(value: &Value) -> String {
|
||||||
|
match value {
|
||||||
|
Value::List(v) => {
|
||||||
|
let strings: Vec<String> = v.iter().map(print).collect();
|
||||||
|
let mut string = strings.join(" ");
|
||||||
|
if v.len() < 2 {
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
let end = string.len() + 1;
|
||||||
|
string.insert_str(0, "(");
|
||||||
|
string.insert_str(end, ")");
|
||||||
|
// dbg!(&string);
|
||||||
|
string
|
||||||
|
}
|
||||||
|
Value::String(s) => {
|
||||||
|
let mut s = s.clone();
|
||||||
|
let length = s.len() + 1;
|
||||||
|
s.insert(0, '"');
|
||||||
|
s.insert(length, '"');
|
||||||
|
s.to_string()
|
||||||
|
}
|
||||||
|
Value::Number(n) => n.to_string(),
|
||||||
|
Value::Nil => "nil".to_string(),
|
||||||
|
Value::Function => "fn".to_string(),
|
||||||
|
Value::True => "t".to_string(),
|
||||||
|
Value::Symbol(s) => s.0.clone(),
|
||||||
|
Value::Keyword(k) => {
|
||||||
|
let mut string = k.0.clone();
|
||||||
|
string.insert_str(0, ":");
|
||||||
|
string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crate::reader::read;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use std::fs::read_to_string;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_print() {
|
||||||
|
let string = read_to_string("./test_presentation.lisp").unwrap();
|
||||||
|
let values = read(&string);
|
||||||
|
let string = r#"((slide :background (image :source "~/pics/frodo.jpg" :fit crop) (text "This is frodo" :font-size 50)) (slide (video :source "~/vids/test/chosensmol.mp4" :fit fill)) (song :author "Jordan Feliz" :ccli 97987 :font "Quicksand" :font-size 80 :title "The River" :background (image :source "./coolbg.jpg") (text "I'm going down to the river") (text "Down to the river") (text "Down to the river to pray ay ay!")) (song :author "Jordan Feliz" :ccli 97987 :font "Quicksand" :font-size 80 :title "The River" :background (video :source "./coolerbg.mkv" :fit cover) :verse-order (v1 c1 v2 c1) (v1 "I'm going down to the river") (c1 "Down to the river") (v2 "Down to the river to pray ay ay!")) (load "./10000-reasons.lisp"))"#.to_string();
|
||||||
|
let print = print(&values);
|
||||||
|
assert_eq!(print, string)
|
||||||
|
}
|
||||||
|
}
|
180
src/reader.rs
180
src/reader.rs
|
@ -1,6 +1,7 @@
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
use crate::types::Symbol;
|
use crate::types::Symbol;
|
||||||
|
use crate::Keyword;
|
||||||
use crate::Value;
|
use crate::Value;
|
||||||
use color_eyre::{eyre::eyre, Result};
|
use color_eyre::{eyre::eyre, Result};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
@ -14,68 +15,84 @@ struct Reader {
|
||||||
|
|
||||||
impl Reader {
|
impl Reader {
|
||||||
fn next(&mut self) -> Option<&String> {
|
fn next(&mut self) -> Option<&String> {
|
||||||
let token = self.position;
|
|
||||||
self.position += 1;
|
self.position += 1;
|
||||||
self.tokens.get(token)
|
self.tokens.get(self.position - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn peek(&self) -> Option<&String> {
|
fn peek(&self) -> Option<&String> {
|
||||||
let index = self.position;
|
self.tokens.get(self.position)
|
||||||
self.tokens.get(index)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_form(&mut self) -> Value {
|
fn read_form(&mut self) -> Value {
|
||||||
let Some(first) = self.peek() else {
|
let Some(token) = self.peek() else {
|
||||||
return Value::Nil;
|
return Value::Nil;
|
||||||
};
|
};
|
||||||
let mut value = Value::Nil;
|
let val;
|
||||||
if first.as_str() == "(" {
|
match &token[..] {
|
||||||
value = self.read_list();
|
"(" => {
|
||||||
} else {
|
val = self.read_list(")");
|
||||||
if let Some(atom) = self.read_atom() {
|
|
||||||
value = atom
|
|
||||||
}
|
}
|
||||||
};
|
"[" => {
|
||||||
value
|
val = self.read_list("]");
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
val = self.read_atom().unwrap_or(Value::Nil);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_atom(&mut self) -> Option<Value> {
|
fn read_atom(&mut self) -> Option<Value> {
|
||||||
let Some(token) = self.next() else {
|
let Some(token) = self.next() else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
if token != "(" {
|
if token != "(" && token != ")" {
|
||||||
Some(Value::from(token))
|
Some(Value::from(token))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_list(&mut self) -> Value {
|
fn read_list(&mut self, end: &str) -> Value {
|
||||||
let mut values = vec![];
|
let mut values = vec![];
|
||||||
let length = self.tokens.len();
|
self.next();
|
||||||
let position = self.position;
|
loop {
|
||||||
while self.tokens.len() >= position {
|
let Some(token) = self.peek() else {
|
||||||
if self.peek().unwrap().as_str() == ")" {
|
return Value::Nil;
|
||||||
|
};
|
||||||
|
if token == end {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
values.push(self.read_form());
|
values.push(self.read_form());
|
||||||
}
|
}
|
||||||
|
self.next();
|
||||||
Value::from(values)
|
Value::from(values)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read(str: &str) -> Value {
|
pub fn read(str: &str) -> Value {
|
||||||
let mut reader = tokenize(str);
|
let mut reader = tokenize(str);
|
||||||
let value = reader.read_form();
|
let mut values = vec![];
|
||||||
value
|
loop {
|
||||||
|
let length = reader.tokens.len();
|
||||||
|
if reader.position <= length {
|
||||||
|
if reader.peek().is_some_and(|s| &s[..] == "(") {
|
||||||
|
values.push(reader.read_form());
|
||||||
|
} else {
|
||||||
|
reader.next();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::from(values)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tokenize(str: &str) -> Reader {
|
fn tokenize(str: &str) -> Reader {
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref RE: Regex = Regex::new(
|
static ref RE: Regex =
|
||||||
r###"[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]+)"###
|
Regex::new(r#"[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)"#)
|
||||||
)
|
.unwrap();
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut res = vec![];
|
let mut res = vec![];
|
||||||
|
@ -85,6 +102,7 @@ fn tokenize(str: &str) -> Reader {
|
||||||
}
|
}
|
||||||
res.push(String::from(&cap[1]));
|
res.push(String::from(&cap[1]));
|
||||||
}
|
}
|
||||||
|
// dbg!(&res);
|
||||||
Reader {
|
Reader {
|
||||||
tokens: res,
|
tokens: res,
|
||||||
position: 0,
|
position: 0,
|
||||||
|
@ -110,15 +128,119 @@ mod test {
|
||||||
String::from("world"),
|
String::from("world"),
|
||||||
String::from(")"),
|
String::from(")"),
|
||||||
];
|
];
|
||||||
|
// let string = get_test_string();
|
||||||
let reader = tokenize("(hello world)");
|
let reader = tokenize("(hello world)");
|
||||||
|
// print!("{:?}", reader);
|
||||||
assert_eq!(test_vec, reader.tokens)
|
assert_eq!(test_vec, reader.tokens)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_read() {
|
fn test_read() {
|
||||||
let test_values = Value::List(vec![Value::Symbol(Symbol(String::from("hello")))]);
|
let test_values = Value::List(vec![
|
||||||
// let values = read(&get_test_string());
|
Value::List(vec![
|
||||||
// let values = read("(hello world)");
|
Value::Symbol(Symbol(String::from("slide"))),
|
||||||
// assert_eq!(test_values, values)
|
Value::Keyword(Keyword(String::from("background"))),
|
||||||
|
Value::List(vec![
|
||||||
|
Value::Symbol(Symbol(String::from("image"))),
|
||||||
|
Value::Keyword(Keyword(String::from("source"))),
|
||||||
|
Value::String(String::from("~/pics/frodo.jpg")),
|
||||||
|
Value::Keyword(Keyword(String::from("fit"))),
|
||||||
|
Value::Symbol(Symbol(String::from("crop"))),
|
||||||
|
]),
|
||||||
|
Value::List(vec![
|
||||||
|
Value::Symbol(Symbol(String::from("text"))),
|
||||||
|
Value::String(String::from("This is frodo")),
|
||||||
|
Value::Keyword(Keyword(String::from("font-size"))),
|
||||||
|
Value::Number(50),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
Value::List(vec![
|
||||||
|
Value::Symbol(Symbol(String::from("slide"))),
|
||||||
|
Value::List(vec![
|
||||||
|
Value::Symbol(Symbol(String::from("video"))),
|
||||||
|
Value::Keyword(Keyword(String::from("source"))),
|
||||||
|
Value::String(String::from("~/vids/test/chosensmol.mp4")),
|
||||||
|
Value::Keyword(Keyword(String::from("fit"))),
|
||||||
|
Value::Symbol(Symbol(String::from("fill"))),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
Value::List(vec![
|
||||||
|
Value::Symbol(Symbol(String::from("song"))),
|
||||||
|
Value::Keyword(Keyword(String::from("author"))),
|
||||||
|
Value::String(String::from("Jordan Feliz")),
|
||||||
|
Value::Keyword(Keyword(String::from("ccli"))),
|
||||||
|
Value::Number(97987),
|
||||||
|
Value::Keyword(Keyword(String::from("font"))),
|
||||||
|
Value::String(String::from("Quicksand")),
|
||||||
|
Value::Keyword(Keyword(String::from("font-size"))),
|
||||||
|
Value::Number(80),
|
||||||
|
Value::Keyword(Keyword(String::from("title"))),
|
||||||
|
Value::String(String::from("The River")),
|
||||||
|
Value::Keyword(Keyword(String::from("background"))),
|
||||||
|
Value::List(vec![
|
||||||
|
Value::Symbol(Symbol(String::from("image"))),
|
||||||
|
Value::Keyword(Keyword(String::from("source"))),
|
||||||
|
Value::String(String::from("./coolbg.jpg")),
|
||||||
|
]),
|
||||||
|
Value::List(vec![
|
||||||
|
Value::Symbol(Symbol(String::from("text"))),
|
||||||
|
Value::String(String::from("I'm going down to the river")),
|
||||||
|
]),
|
||||||
|
Value::List(vec![
|
||||||
|
Value::Symbol(Symbol(String::from("text"))),
|
||||||
|
Value::String(String::from("Down to the river")),
|
||||||
|
]),
|
||||||
|
Value::List(vec![
|
||||||
|
Value::Symbol(Symbol(String::from("text"))),
|
||||||
|
Value::String(String::from("Down to the river to pray ay ay!")),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
Value::List(vec![
|
||||||
|
Value::Symbol(Symbol(String::from("song"))),
|
||||||
|
Value::Keyword(Keyword(String::from("author"))),
|
||||||
|
Value::String(String::from("Jordan Feliz")),
|
||||||
|
Value::Keyword(Keyword(String::from("ccli"))),
|
||||||
|
Value::Number(97987),
|
||||||
|
Value::Keyword(Keyword(String::from("font"))),
|
||||||
|
Value::String(String::from("Quicksand")),
|
||||||
|
Value::Keyword(Keyword(String::from("font-size"))),
|
||||||
|
Value::Number(80),
|
||||||
|
Value::Keyword(Keyword(String::from("title"))),
|
||||||
|
Value::String(String::from("The River")),
|
||||||
|
Value::Keyword(Keyword(String::from("background"))),
|
||||||
|
Value::List(vec![
|
||||||
|
Value::Symbol(Symbol(String::from("video"))),
|
||||||
|
Value::Keyword(Keyword(String::from("source"))),
|
||||||
|
Value::String(String::from("./coolerbg.mkv")),
|
||||||
|
Value::Keyword(Keyword(String::from("fit"))),
|
||||||
|
Value::Symbol(Symbol(String::from("cover"))),
|
||||||
|
]),
|
||||||
|
Value::Keyword(Keyword(String::from("verse-order"))),
|
||||||
|
Value::List(vec![
|
||||||
|
Value::Symbol(Symbol(String::from("v1"))),
|
||||||
|
Value::Symbol(Symbol(String::from("c1"))),
|
||||||
|
Value::Symbol(Symbol(String::from("v2"))),
|
||||||
|
Value::Symbol(Symbol(String::from("c1"))),
|
||||||
|
]),
|
||||||
|
Value::List(vec![
|
||||||
|
Value::Symbol(Symbol(String::from("v1"))),
|
||||||
|
Value::String(String::from("I'm going down to the river")),
|
||||||
|
]),
|
||||||
|
Value::List(vec![
|
||||||
|
Value::Symbol(Symbol(String::from("c1"))),
|
||||||
|
Value::String(String::from("Down to the river")),
|
||||||
|
]),
|
||||||
|
Value::List(vec![
|
||||||
|
Value::Symbol(Symbol(String::from("v2"))),
|
||||||
|
Value::String(String::from("Down to the river to pray ay ay!")),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
Value::List(vec![
|
||||||
|
Value::Symbol(Symbol(String::from("load"))),
|
||||||
|
Value::String(String::from("./10000-reasons.lisp")),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
let values = read(&get_test_string());
|
||||||
|
assert_eq!(test_values, values)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ pub enum Value {
|
||||||
#[default]
|
#[default]
|
||||||
Nil,
|
Nil,
|
||||||
Function,
|
Function,
|
||||||
|
True,
|
||||||
Symbol(Symbol),
|
Symbol(Symbol),
|
||||||
Keyword(Keyword),
|
Keyword(Keyword),
|
||||||
}
|
}
|
||||||
|
@ -49,8 +50,14 @@ impl From<&str> for Value {
|
||||||
Value::Number(parse_result)
|
Value::Number(parse_result)
|
||||||
} else if let Some(s) = s.strip_prefix(":") {
|
} else if let Some(s) = s.strip_prefix(":") {
|
||||||
Value::Keyword(Keyword(s.to_string()))
|
Value::Keyword(Keyword(s.to_string()))
|
||||||
|
} else if s == "t".to_owned() {
|
||||||
|
Value::True
|
||||||
} else if s.starts_with(r#"""#) {
|
} else if s.starts_with(r#"""#) {
|
||||||
Value::String(s)
|
Value::String(s.replace('"', ""))
|
||||||
|
} else if s == "fn".to_owned() {
|
||||||
|
Value::Function
|
||||||
|
} else if s == "defun".to_owned() {
|
||||||
|
Value::Function
|
||||||
} else if s == "nil".to_owned() {
|
} else if s == "nil".to_owned() {
|
||||||
Value::Nil
|
Value::Nil
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue