diff options
author | Bryan Newbold <bnewbold@robocracy.org> | 2021-10-24 11:51:03 -0700 |
---|---|---|
committer | Bryan Newbold <bnewbold@robocracy.org> | 2021-10-24 11:51:03 -0700 |
commit | 76a5c98ef5588e94301f2d1b44b51907e5004abe (patch) | |
tree | 28e62f093f1950c0db2b2e51328aaf9ef6417350 /src | |
parent | 5f501b3654d6b36e850044ba492dd7bc7ee56438 (diff) | |
download | casual-76a5c98ef5588e94301f2d1b44b51907e5004abe.tar.gz casual-76a5c98ef5588e94301f2d1b44b51907e5004abe.zip |
more progress
Diffstat (limited to 'src')
-rw-r--r-- | src/cexpr.rs | 159 | ||||
-rw-r--r-- | src/main.rs | 33 | ||||
-rw-r--r-- | src/sexpr.rs | 73 |
3 files changed, 159 insertions, 106 deletions
diff --git a/src/cexpr.rs b/src/cexpr.rs index 39c708e..1971feb 100644 --- a/src/cexpr.rs +++ b/src/cexpr.rs @@ -5,15 +5,33 @@ * */ -use std::fmt; use crate::sexpr::SExpr; +use std::fmt; -/* +#[derive(Debug, Clone, PartialEq, PartialOrd)] pub enum NumericConstant { Pi, // 3.141592... E, // 2.718281... } -*/ + +impl NumericConstant { + pub fn as_number(&self) -> CNumber { + use CNumber::*; + use NumericConstant::*; + match self { + Pi => Float(3.141592f64), + E => Float(2.718281f64), + } + } + + pub fn as_string(&self) -> String { + use NumericConstant::*; + match self { + Pi => "pi".to_string(), + E => "e".to_string(), + } + } +} #[derive(Clone, PartialEq, PartialOrd)] pub enum CNumber { @@ -41,6 +59,7 @@ impl CNumber { pub enum CExpr { // order here is important for sorting, etc Number(CNumber), + Constant(NumericConstant), Symbol(String), Sum(Option<CNumber>, Vec<CExpr>), Product(Option<CNumber>, Vec<CExpr>), @@ -50,28 +69,7 @@ pub enum CExpr { } impl CExpr { - -/* - fn sort_type_val(&self) -> u8 { - use CExpr::*; - match self { - Number(Integer(_)) => 1, - Number(Rational(_)) => 2, - Number(Float(_)) => 3, - Symbol(_) => 3, - Sum(Some(_), _) => 4, - Sum(None, _) => 4, - Product(Some(_), _) => 4, - Product(None, _) => 4, - Exponent(_, _) => 4, - Factorial(_) => 4, - UnaryFunction(_, _) => 4, - } - } -*/ - pub fn from_sexpr(sexpr: &SExpr) -> Result<CExpr, String> { - // not all cases are handled; some atoms are covered trivialy match sexpr { SExpr::SNull => Err("null not handled".to_string()), @@ -79,7 +77,11 @@ impl CExpr { SExpr::SInteger(v) => Ok(CExpr::Number(CNumber::Integer(*v))), SExpr::SFloat(v) => Ok(CExpr::Number(CNumber::Float(*v))), SExpr::SString(_) => Err("null not handled".to_string()), - SExpr::SIdentifier(v) => Ok(CExpr::Symbol(v.to_string())), + SExpr::SIdentifier(v) => match v.as_str() { + "pi" => Ok(CExpr::Constant(NumericConstant::Pi)), + "e" => Ok(CExpr::Constant(NumericConstant::E)), + _ => Ok(CExpr::Symbol(v.to_string())), + }, SExpr::SList(l) => CExpr::from_sexpr_list(l), } } @@ -88,10 +90,11 @@ impl CExpr { // https://adventures.michaelfbryan.com/posts/daily/slice-patterns/ if let [SExpr::SIdentifier(ident), rest @ ..] = list.as_slice() { match (ident.as_str(), rest.len()) { - ("factorial", 1) => - Ok(CExpr::Factorial(Box::new(CExpr::from_sexpr(&rest[0])?))), - ("cos" | "sin" | "tan", 1) => - Ok(CExpr::UnaryFunction(ident.to_string(), Box::new(CExpr::from_sexpr(&rest[0])?))), + ("factorial", 1) => Ok(CExpr::Factorial(Box::new(CExpr::from_sexpr(&rest[0])?))), + ("cos" | "sin" | "tan", 1) => Ok(CExpr::UnaryFunction( + ident.to_string(), + Box::new(CExpr::from_sexpr(&rest[0])?), + )), ("^", 2) => { let base = CExpr::from_sexpr(&rest[0])?; let power = CExpr::from_sexpr(&rest[1])?; @@ -104,27 +107,63 @@ impl CExpr { (base, Number(Integer(1))) => Ok(base), (base, power) => Ok(Exponent(Box::new(base), Box::new(power))), } - }, + } ("/", 2) => { - if let (SExpr::SInteger(numer), SExpr::SInteger(denom)) = (&rest[0], &rest[1]) { - match (numer, denom) { - (_, 0) => Err("division by zero".to_string()), - (0, _) => Ok(CExpr::Number(CNumber::Integer(0))), - (a, 1) => Ok(CExpr::Number(CNumber::Integer(*a))), - (a, b) if a == b => Ok(CExpr::Number(CNumber::Integer(1))), - (a, b) if a % b == 0 => Ok(CExpr::Number(CNumber::Integer(a/b))), - _ => Ok(CExpr::Number(CNumber::Rational(*numer, *denom))), + use CExpr::*; + use CNumber::*; + use SExpr::*; + match (&rest[0], &rest[1]) { + (_, SInteger(0)) => Err("division by zero".to_string()), + (SInteger(0), _) => Ok(Number(Integer(0))), + (se, SInteger(1)) => CExpr::from_sexpr(se), + (SInteger(a), SInteger(b)) if a == b => Ok(Number(Integer(1))), + (SInteger(a), SInteger(b)) if a % b == 0 => Ok(Number(Integer(a / b))), + (SInteger(a), SInteger(b)) => Ok(Number(Rational(*a, *b))), + (SInteger(1), b) => { + let denom = CExpr::from_sexpr(b)?; + Ok(Exponent(Box::new(denom), Box::new(Number(Integer(-1))))) + } + (a, b) => { + let (numer, denom) = (CExpr::from_sexpr(a)?, CExpr::from_sexpr(b)?); + CExpr::new_product(vec![ + numer, + Exponent(Box::new(denom), Box::new(Number(Integer(-1)))), + ]) } - } else { - Err("only integers in fractions supported".to_string()) } - }, + } // TODO: how to make range unbounded? or less bounded? - ("+", 2..=5000) => CExpr::new_sum(rest.iter().map(|v| CExpr::from_sexpr(v)).collect::<Result<Vec<CExpr>, String>>()?), - ("*", 2..=5000) => CExpr::new_product(rest.iter().map(|v| CExpr::from_sexpr(v)).collect::<Result<Vec<CExpr>, String>>()?), - // TODO: subtraction, division - ("factorial" | "^" | "cos" | "sin" | "tan", count) => - Err(format!("wrong number of arguments to {}: {}", ident, count)), + ("+", 2..=5000) => { + CExpr::new_sum(rest.iter().map(|v| CExpr::from_sexpr(v)).collect::<Result< + Vec<CExpr>, + String, + >>( + )?) + } + ("*", 2..=5000) => { + CExpr::new_product(rest.iter().map(|v| CExpr::from_sexpr(v)).collect::<Result< + Vec<CExpr>, + String, + >>( + )?) + } + ("-", 2) => { + let a = CExpr::from_sexpr(&rest[0])?; + let b = CExpr::from_sexpr(&rest[1])?; + use CExpr::*; + use CNumber::*; + match (a, b) { + (expr, Number(Integer(0))) => Ok(expr), + (Number(Integer(a)), Number(Integer(b))) => Ok(Number(Integer(a - b))), + (a, b) => CExpr::new_sum(vec![ + a, + CExpr::new_product(vec![Number(Integer(-1)), b])?, + ]), + } + } + ("factorial" | "^" | "cos" | "sin" | "tan", count) => { + Err(format!("wrong number of arguments to {}: {}", ident, count)) + } _ => Err(format!("procedure not handled: {}", ident)), } } else { @@ -138,9 +177,9 @@ impl CExpr { list.sort_by(|a, b| a.partial_cmp(b).unwrap()); match list.as_slice() { [Number(Integer(0)), e] => Ok(e.clone()), // XXX: remove clone() - [Number(Integer(a)), Number(Integer(b))] => Ok(Number(Integer(a+b))), - [Number(Integer(a)), Number(Rational(n, d))] => Ok(Number(Rational(n+a*d, *d))), - _ => Ok(Sum(None, list)) + [Number(Integer(a)), Number(Integer(b))] => Ok(Number(Integer(a + b))), + [Number(Integer(a)), Number(Rational(n, d))] => Ok(Number(Rational(n + a * d, *d))), + _ => Ok(Sum(None, list)), } } @@ -150,9 +189,9 @@ impl CExpr { list.sort_by(|a, b| a.partial_cmp(b).unwrap()); match list.as_slice() { [Number(Integer(1)), e] => Ok(e.clone()), // XXX: remove clone() - [Number(Integer(a)), Number(Integer(b))] => Ok(Number(Integer(a*b))), - [Number(Integer(a)), Number(Rational(n, d))] => Ok(Number(Rational(a*n, *d))), - _ => Ok(Product(None, list)) + [Number(Integer(a)), Number(Integer(b))] => Ok(Number(Integer(a * b))), + [Number(Integer(a)), Number(Rational(n, d))] => Ok(Number(Rational(a * n, *d))), + _ => Ok(Product(None, list)), } } @@ -160,22 +199,29 @@ impl CExpr { match self { CExpr::Symbol(s) => Ok(SExpr::SIdentifier(s.to_string())), CExpr::Number(n) => n.to_sexpr(), + CExpr::Constant(n) => Ok(SExpr::SIdentifier(n.as_string())), CExpr::Sum(n, l) => { - let mut list = l.iter().map(|v| v.to_sexpr()).collect::<Result<Vec<SExpr>, String>>()?; + let mut list = l + .iter() + .map(|v| v.to_sexpr()) + .collect::<Result<Vec<SExpr>, String>>()?; list.insert(0, SExpr::SIdentifier("+".to_string())); if let Some(num) = n { list.insert(1, num.to_sexpr()?); } Ok(SExpr::SList(list)) - }, + } CExpr::Product(n, l) => { - let mut list = l.iter().map(|v| v.to_sexpr()).collect::<Result<Vec<SExpr>, String>>()?; + let mut list = l + .iter() + .map(|v| v.to_sexpr()) + .collect::<Result<Vec<SExpr>, String>>()?; list.insert(0, SExpr::SIdentifier("*".to_string())); if let Some(num) = n { list.insert(1, num.to_sexpr()?); } Ok(SExpr::SList(list)) - }, + } CExpr::Exponent(a, b) => Ok(SExpr::SList(vec![ SExpr::SIdentifier("^".to_string()), a.to_sexpr()?, @@ -206,4 +252,3 @@ impl fmt::Display for CExpr { } } } - diff --git a/src/main.rs b/src/main.rs index 9a025b9..f09e0f0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,14 @@ - -use std::io; use std::env; +use std::io; use std::io::Write; use std::path::Path; -mod sexpr; mod cexpr; +mod sexpr; use cexpr::CExpr; fn repl(_verbose: bool) { - let stdin = io::stdin(); let mut stdout = io::stdout(); @@ -40,12 +38,13 @@ fn usage() { println!("usage:\tcasual [-h] [-v] [--no-repl] [<files>]"); println!(""); println!("Files will be loaded in order, then drop to REPL (unless \"--no-repl\" is passed)."); - println!("Verbose flag (\"-v\") will result in lexed tokens and parsed AST \ - being dumped to stdout (when on REPL)."); + println!( + "Verbose flag (\"-v\") will result in lexed tokens and parsed AST \ + being dumped to stdout (when on REPL)." + ); } fn main() { - let mut verbose: bool = false; let mut no_repl: bool = false; @@ -53,15 +52,22 @@ fn main() { for arg in env::args().skip(1) { match &*arg { - "-v" | "--verbose" => { verbose = true; }, - "--no-repl" => { no_repl = true; }, - "-h" | "--help" => { usage(); return; }, + "-v" | "--verbose" => { + verbose = true; + } + "--no-repl" => { + no_repl = true; + } + "-h" | "--help" => { + usage(); + return; + } _ if arg.starts_with("-") => { println!("Unknown option: {}", arg); println!(""); usage(); return; - }, + } _ => { file_list.push(arg.clone()); } @@ -79,8 +85,8 @@ fn main() { Err(e) => { println!("Error loading file: {}\n {}", fname, e); return; - }, - Ok(_) => () + } + Ok(_) => (), } } @@ -88,4 +94,3 @@ fn main() { repl(verbose); } } - diff --git a/src/sexpr.rs b/src/sexpr.rs index d761192..7312b68 100644 --- a/src/sexpr.rs +++ b/src/sexpr.rs @@ -9,11 +9,11 @@ * in a dependency. */ -use std::str; use std::fmt; -use std::io::Read; use std::fs::File; +use std::io::Read; use std::path::Path; +use std::str; //////////// Types and Constants @@ -30,7 +30,7 @@ pub enum SExpr { //////////// Lexing, Parsing, and Printing -fn is_whitespace(c: char) -> bool{ +fn is_whitespace(c: char) -> bool { " \t\r\n".find(c) != None } @@ -47,11 +47,15 @@ fn is_valid_identifier(s: &str) -> bool { if s == "." { return false; } + if s == "-" { + return true; + } if s.starts_with("-") || s.starts_with("'") { return false; } for (i, c) in s.chars().enumerate() { - if !( c.is_alphabetic() || "!$%&*+-./:<=>?@^_~".find(c) != None || (c.is_numeric() && i > 0) ) { + if !(c.is_alphabetic() || "!$%&*+-./:<=>?@^_~".find(c) != None || (c.is_numeric() && i > 0)) + { return false; } } @@ -64,17 +68,17 @@ fn is_valid_identifier(s: &str) -> bool { */ pub fn sexpr_tokenize<'a>(raw_str: &'a str) -> Result<Vec<&'a str>, String> { let mut ret = Vec::<&str>::new(); - let mut food: usize = 0; // "how many chars of current token have we read?" + let mut food: usize = 0; // "how many chars of current token have we read?" let mut quoted: bool = false; let mut commented: bool = false; for (i, c) in raw_str.chars().enumerate() { if quoted { // Safe to look-back a character here because quoted can't be true for first char - if c == '"' && raw_str.chars().collect::<Vec<char>>()[i-1] != '\\' { - ret.push(&raw_str[i-food-1..i+1]); + if c == '"' && raw_str.chars().collect::<Vec<char>>()[i - 1] != '\\' { + ret.push(&raw_str[i - food - 1..i + 1]); quoted = false; food = 0; - } else if raw_str.len() == i+1 { + } else if raw_str.len() == i + 1 { return Err(format!("unmatched quote char")); } else { food += 1; @@ -85,7 +89,7 @@ pub fn sexpr_tokenize<'a>(raw_str: &'a str) -> Result<Vec<&'a str>, String> { } } else if c == ';' { if food > 0 { - ret.push(&raw_str[i-food..i]); + ret.push(&raw_str[i - food..i]); } commented = true; food = 0; @@ -96,15 +100,15 @@ pub fn sexpr_tokenize<'a>(raw_str: &'a str) -> Result<Vec<&'a str>, String> { quoted = true; } else if is_whitespace(c) || is_seperator(c) { if food > 0 { - ret.push(&raw_str[i-food..i]); + ret.push(&raw_str[i - food..i]); } if is_seperator(c) { - ret.push(&raw_str[i..i+1]); + ret.push(&raw_str[i..i + 1]); } food = 0; - } else if raw_str.len() == i+1 { + } else if raw_str.len() == i + 1 { // end of input - ret.push(&raw_str[i-food..]); + ret.push(&raw_str[i - food..]); } else { food += 1; } @@ -119,24 +123,23 @@ pub fn sexpr_tokenize<'a>(raw_str: &'a str) -> Result<Vec<&'a str>, String> { * This function takes a token (still a string) and parses it into a single SExpression */ fn sexpr_parse_token(token: &str) -> Result<SExpr, String> { - // Is it a constant? match token { "#t" => return Ok(SExpr::SBoolean(true)), "#f" => return Ok(SExpr::SBoolean(false)), - _ => () + _ => (), } // Try to parse as an integer match token.parse::<i64>() { Ok(x) => return Ok(SExpr::SInteger(x)), - Err(_) => () + Err(_) => (), } // Try to parse as floating-point number match token.parse::<f64>() { Ok(x) => return Ok(SExpr::SFloat(x)), - Err(_) => () + Err(_) => (), } // Is it a string? @@ -158,7 +161,7 @@ fn sexpr_parse_token(token: &str) -> Result<SExpr, String> { */ pub fn sexpr_parse(tokens: &Vec<&str>, depth: u32) -> Result<(Vec<SExpr>, usize), String> { let mut i: usize = 0; - if tokens.len() == 0 { + if tokens.len() == 0 { return Ok((vec![SExpr::SNull], 0)); } else if tokens.len() == 1 { let expr = sexpr_parse_token(tokens[0])?; @@ -171,25 +174,24 @@ pub fn sexpr_parse(tokens: &Vec<&str>, depth: u32) -> Result<(Vec<SExpr>, usize) match tokens[i] { "(" => { // "Read ahead" to check for empty tuple - if i+1 < tokens.len() && tokens[i+1] == ")" { + if i + 1 < tokens.len() && tokens[i + 1] == ")" { ret.push(SExpr::SNull); i += 1; parsed += 1; } else { - let (expr_list, skip) = sexpr_parse(&tokens[i+1..].to_vec(), depth+1)?; + let (expr_list, skip) = sexpr_parse(&tokens[i + 1..].to_vec(), depth + 1)?; i += skip; parsed += skip; ret.push(SExpr::SList(expr_list)); } - }, + } ")" => { if depth == 0 { return Err(format!("missing an open bracket")); } return Ok((ret, parsed)); - }, - "'" => { - }, + } + "'" => {} token => { let expr = sexpr_parse_token(token)?; ret.push(expr); @@ -215,25 +217,27 @@ pub fn sexpr_repr(ast: &SExpr) -> Result<String, String> { &SExpr::SBoolean(false) => Ok("#f".to_string()), &SExpr::SInteger(num) => Ok(format!("{}", num).to_string()), &SExpr::SFloat(num) => Ok(format!("{}", num).to_string()), - &SExpr::SString(ref s)=> Ok(s.clone()), - &SExpr::SIdentifier(ref s)=> Ok(s.to_string()), + &SExpr::SString(ref s) => Ok(s.clone()), + &SExpr::SIdentifier(ref s) => Ok(s.to_string()), &SExpr::SList(ref list) => { - let elements: Vec<String> = list.iter().map(|ref el| sexpr_repr(&el).unwrap()).collect(); + let elements: Vec<String> = + list.iter().map(|ref el| sexpr_repr(&el).unwrap()).collect(); Ok(format!("({})", elements.join(" "))) - }, - } + } + }; } pub fn sexpr_parse_file(fpath: &Path) -> Result<(), String> { - let mut raw_bytes: Vec<u8> = Vec::new(); - let mut f = File::open(fpath) - .expect(&format!("couldn't open file: {}", &fpath.to_str().unwrap())); + let mut f = + File::open(fpath).expect(&format!("couldn't open file: {}", &fpath.to_str().unwrap())); f.read_to_end(&mut raw_bytes) .expect(&format!("couldn't read file: {}", &fpath.to_str().unwrap())); - let contents = String::from_utf8(raw_bytes) - .expect(&format!("UTF-8 decode error reading file: {}", &fpath.to_str().unwrap())); + let contents = String::from_utf8(raw_bytes).expect(&format!( + "UTF-8 decode error reading file: {}", + &fpath.to_str().unwrap() + )); let tokens = sexpr_tokenize(&contents)?; let (_ast_list, _) = sexpr_parse(&tokens, 0)?; @@ -256,4 +260,3 @@ impl fmt::Display for SExpr { } } } - |