aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBryan Newbold <bnewbold@robocracy.org>2021-10-24 11:51:03 -0700
committerBryan Newbold <bnewbold@robocracy.org>2021-10-24 11:51:03 -0700
commit76a5c98ef5588e94301f2d1b44b51907e5004abe (patch)
tree28e62f093f1950c0db2b2e51328aaf9ef6417350
parent5f501b3654d6b36e850044ba492dd7bc7ee56438 (diff)
downloadcasual-76a5c98ef5588e94301f2d1b44b51907e5004abe.tar.gz
casual-76a5c98ef5588e94301f2d1b44b51907e5004abe.zip
more progress
-rw-r--r--src/cexpr.rs159
-rw-r--r--src/main.rs33
-rw-r--r--src/sexpr.rs73
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 {
}
}
}
-