Last active
March 7, 2023 04:24
-
-
Save stopachka/ddedb35ca7fc9ea5dd320c218e9054c8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::collections::HashMap; | |
use std::io; | |
use std::num::ParseFloatError; | |
/* | |
Types | |
*/ | |
#[derive(Clone)] | |
enum RispExp { | |
Bool(bool), | |
Symbol(String), | |
Number(f64), | |
List(Vec<RispExp>), | |
Func(fn(&[RispExp]) -> Result<RispExp, RispErr>), | |
} | |
#[derive(Debug)] | |
enum RispErr { | |
Reason(String), | |
} | |
#[derive(Clone)] | |
struct RispEnv { | |
data: HashMap<String, RispExp>, | |
} | |
/* | |
Parse | |
*/ | |
fn tokenize(expr: String) -> Vec<String> { | |
return expr | |
.replace("(", " ( ") | |
.replace(")", " ) ") | |
.split(" ") | |
.map(|x| x.trim().to_string()) | |
.filter(|x| !x.is_empty()) | |
.collect(); | |
} | |
fn parse(tokens: &[String], pos: usize) -> Result<(RispExp, usize), RispErr> { | |
let token = tokens | |
.get(pos) | |
.ok_or( | |
RispErr::Reason(format!("could not get token for pos='{}'", pos)) | |
)?; | |
let to_match = &token[..]; | |
match to_match { | |
"(" => read_seq(tokens, pos + 1), | |
")" => Err(RispErr::Reason("unexpected `)`".to_string())), | |
_ => Ok( | |
(parse_atom(token), pos + 1) | |
), | |
} | |
} | |
fn read_seq(tokens: &[String], start: usize) -> Result<(RispExp, usize), RispErr> { | |
let mut res: Vec<RispExp> = vec![]; | |
let mut next = start; | |
loop { | |
let next_token = tokens | |
.get(next) | |
.ok_or(RispErr::Reason("could not find closing `)`".to_string())) | |
?; | |
if next_token == ")" { | |
return Ok((RispExp::List(res), next + 1)) // skip `)`, head to the token after | |
} | |
let (exp, new_next) = parse(&tokens, next)?; | |
res.push(exp); | |
next = new_next; | |
} | |
} | |
fn parse_atom(token: &str) -> RispExp { | |
let potential_float: Result<f64, ParseFloatError> = token.parse(); | |
return match potential_float { | |
Ok(v) => RispExp::Number(v), | |
Err(_) => RispExp::Symbol(token.to_string().clone()) | |
} | |
} | |
/* | |
Env | |
*/ | |
macro_rules! ensure_tonicity { | |
($check_fn:expr) => {{ | |
|args: &[RispExp]| -> Result<RispExp, RispErr> { | |
let floats = parse_list_of_floats(args)?; | |
let first = floats.first().ok_or(RispErr::Reason("expected at least one number".to_string()))?; | |
let rest = &floats[1..]; | |
fn f (prev: &f64, xs: &[f64]) -> bool { | |
match xs.first() { | |
Some(x) => $check_fn(prev, x) && f(x, &xs[1..]), | |
None => true, | |
} | |
}; | |
return Ok(RispExp::Bool(f(first, rest))); | |
} | |
}}; | |
} | |
fn default_env() -> RispEnv { | |
let mut data: HashMap<String, RispExp> = HashMap::new(); | |
data.insert( | |
"+".to_string(), | |
RispExp::Func( | |
|args: &[RispExp]| -> Result<RispExp, RispErr> { | |
let sum = parse_list_of_floats(args)?.iter().fold(0.0, |sum, a| sum + a); | |
return Ok(RispExp::Number(sum)); | |
} | |
) | |
); | |
data.insert( | |
"-".to_string(), | |
RispExp::Func( | |
|args: &[RispExp]| -> Result<RispExp, RispErr> { | |
let floats = parse_list_of_floats(args)?; | |
let first = *floats.first().ok_or(RispErr::Reason("expected at least one number".to_string()))?; | |
let sum_of_rest = floats[1..].iter().fold(0.0, |sum, a| sum + a); | |
return Ok(RispExp::Number(first - sum_of_rest)); | |
} | |
) | |
); | |
data.insert( | |
"=".to_string(), | |
RispExp::Func(ensure_tonicity!(|a, b| a == b)) | |
); | |
data.insert( | |
">".to_string(), | |
RispExp::Func(ensure_tonicity!(|a, b| a > b)) | |
); | |
data.insert( | |
">=".to_string(), | |
RispExp::Func(ensure_tonicity!(|a, b| a >= b)) | |
); | |
data.insert( | |
"<".to_string(), | |
RispExp::Func(ensure_tonicity!(|a, b| a < b)) | |
); | |
data.insert( | |
"<=".to_string(), | |
RispExp::Func(ensure_tonicity!(|a, b| a <= b)) | |
); | |
return RispEnv {data: data} | |
} | |
fn parse_list_of_floats(args: &[RispExp]) -> Result<Vec<f64>, RispErr> { | |
return args | |
.iter() | |
.map(|x| parse_single_float(x)) | |
.collect::<Result<Vec<f64>, RispErr>>(); | |
} | |
fn parse_single_float(exp: &RispExp) -> Result<f64, RispErr> { | |
match exp { | |
RispExp::Number(num) => Ok(*num), | |
_ => Err(RispErr::Reason("expected a number".to_string())), | |
} | |
} | |
/* | |
Eval | |
*/ | |
fn eval(exp: &RispExp, env: &mut RispEnv) -> Result<RispExp, RispErr> { | |
match exp { | |
RispExp::Symbol(k) => | |
env.data.get(k) | |
.ok_or( | |
RispErr::Reason( | |
format!("unexpected symbol k='{}'", k) | |
) | |
) | |
.map(|x| x.clone()) | |
, | |
RispExp::Number(_a) => Ok(exp.clone()), | |
RispExp::Bool(_a) => Ok(exp.clone()), | |
RispExp::List(list) => { | |
let first_form = list | |
.first() | |
.ok_or(RispErr::Reason("expected a non-empty list".to_string()))?; | |
let arg_forms = &list[1..]; | |
let first_eval = eval(first_form, env)?; | |
return match first_eval { | |
RispExp::Func(f) => { | |
let args_eval = arg_forms | |
.iter() | |
.map(|x| eval(x, env)) | |
.collect::<Result<Vec<RispExp>, RispErr>>(); | |
return f(&args_eval?); | |
}, | |
_ => Err( | |
RispErr::Reason("first form must be a function".to_string()) | |
), | |
} | |
}, | |
RispExp::Func(_) => Err( | |
RispErr::Reason("unexpected form".to_string()) | |
), | |
} | |
} | |
/* | |
Repl | |
*/ | |
fn to_str(exp: &RispExp) -> String { | |
match exp { | |
RispExp::Bool(b) => b.to_string(), | |
RispExp::Symbol(s) => s.clone(), | |
RispExp::Number(n) => n.to_string(), | |
RispExp::List(list) => { | |
let xs: Vec<String> = list | |
.iter() | |
.map(|x| to_str(x)) | |
.collect(); | |
return format!("({})", xs.join(",")); | |
}, | |
RispExp::Func(_) => "Function {}".to_string(), | |
} | |
} | |
fn parse_eval_print(expr: String, env: &mut RispEnv) -> Result<String, RispErr> { | |
let (parsed_exp, _) = parse(&tokenize(expr), 0)?; | |
let evaled_exp = eval(&parsed_exp, env)?; | |
return Ok(to_str(&evaled_exp)); | |
} | |
fn slurp_expr() -> String { | |
let mut expr = String::new(); | |
io::stdin().read_line(&mut expr) | |
.expect("Failed to read line"); | |
return expr; | |
} | |
fn main() { | |
let env = &mut default_env(); | |
loop { | |
println!("risp >"); | |
let expr = slurp_expr();; | |
match parse_eval_print(expr, env) { | |
Ok(res) => println!("// 🔥 => {}", res), | |
Err(e) => match e { | |
RispErr::Reason(msg) => println!("// 🙀 => {}", msg), | |
}, | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment