Last active
January 17, 2022 10:30
-
-
Save stopachka/22b4b06b8263687d7178f61fb22e1bf2 to your computer and use it in GitHub Desktop.
This file contains 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::fmt; | |
use std::io; | |
use std::num::ParseFloatError; | |
/* | |
Types | |
*/ | |
#[derive(Clone)] | |
enum RispExp { | |
Symbol(String), | |
Number(f64), | |
List(Vec<RispExp>), | |
Func(fn(&[RispExp]) -> Result<RispExp, RispErr>), | |
} | |
impl fmt::Display for RispExp { | |
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
let str = match self { | |
RispExp::Symbol(s) => s.clone(), | |
RispExp::Number(n) => n.to_string(), | |
RispExp::List(list) => { | |
let xs: Vec<String> = list | |
.iter() | |
.map(|x| x.to_string()) | |
.collect(); | |
format!("({})", xs.join(",")) | |
}, | |
RispExp::Func(_) => "Function {}".to_string(), | |
}; | |
write!(f, "{}", str) | |
} | |
} | |
#[derive(Debug)] | |
enum RispErr { | |
Reason(String), | |
} | |
#[derive(Clone)] | |
struct RispEnv { | |
data: HashMap<String, RispExp>, | |
} | |
/* | |
Parse | |
*/ | |
fn tokenize(expr: String) -> Vec<String> { | |
expr | |
.replace("(", " ( ") | |
.replace(")", " ) ") | |
.split_whitespace() | |
.map(|x| x.to_string()) | |
.collect() | |
} | |
fn parse<'a>(tokens: &'a [String]) -> Result<(RispExp, &'a [String]), RispErr> { | |
let (token, rest) = tokens.split_first() | |
.ok_or( | |
RispErr::Reason("could not get token".to_string()) | |
)?; | |
match &token[..] { | |
"(" => read_seq(rest), | |
")" => Err(RispErr::Reason("unexpected `)`".to_string())), | |
_ => Ok((parse_atom(token), rest)), | |
} | |
} | |
fn read_seq<'a>(tokens: &'a [String]) -> Result<(RispExp, &'a [String]), RispErr> { | |
let mut res: Vec<RispExp> = vec![]; | |
let mut xs = tokens; | |
loop { | |
let (next_token, rest) = xs | |
.split_first() | |
.ok_or(RispErr::Reason("could not find closing `)`".to_string())) | |
?; | |
if next_token == ")" { | |
return Ok((RispExp::List(res), rest)) // skip `)`, head to the token after | |
} | |
let (exp, new_xs) = parse(&xs)?; | |
res.push(exp); | |
xs = new_xs; | |
} | |
} | |
fn parse_atom(token: &str) -> RispExp { | |
let potential_float: Result<f64, ParseFloatError> = token.parse(); | |
match potential_float { | |
Ok(v) => RispExp::Number(v), | |
Err(_) => RispExp::Symbol(token.to_string().clone()) | |
} | |
} | |
/* | |
Env | |
*/ | |
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); | |
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); | |
Ok(RispExp::Number(first - sum_of_rest)) | |
} | |
) | |
); | |
RispEnv {data} | |
} | |
fn parse_list_of_floats(args: &[RispExp]) -> Result<Vec<f64>, RispErr> { | |
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::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)?; | |
match first_eval { | |
RispExp::Func(f) => { | |
let args_eval = arg_forms | |
.iter() | |
.map(|x| eval(x, env)) | |
.collect::<Result<Vec<RispExp>, RispErr>>(); | |
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 parse_eval(expr: String, env: &mut RispEnv) -> Result<RispExp, RispErr> { | |
let (parsed_exp, _) = parse(&tokenize(expr))?; | |
let evaled_exp = eval(&parsed_exp, env)?; | |
Ok(evaled_exp) | |
} | |
fn slurp_expr() -> String { | |
let mut expr = String::new(); | |
io::stdin().read_line(&mut expr) | |
.expect("Failed to read line"); | |
expr | |
} | |
fn main() { | |
let env = &mut default_env(); | |
loop { | |
println!("risp >"); | |
let expr = slurp_expr();; | |
match parse_eval(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