├── .gitignore ├── Cargo.lock ├── README.md ├── Cargo.toml ├── LICENSE └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | **/*.rs.bk 3 | /target/ 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "risp" 3 | version = "0.1.0" 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Risp 2 | 3 | The code behind [this essay](https://m.stopa.io/risp-lisp-in-rust-90a0dad5b116) 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "risp" 3 | version = "0.1.0" 4 | authors = ["stopachka "] 5 | edition = "2018" 6 | 7 | [dependencies] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Stepan Parunashvili 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fmt; 3 | use std::io; 4 | use std::num::ParseFloatError; 5 | use std::rc::Rc; 6 | 7 | /* 8 | Types 9 | */ 10 | 11 | #[derive(Clone)] 12 | enum RispExp { 13 | Bool(bool), 14 | Symbol(String), 15 | Number(f64), 16 | List(Vec), 17 | Func(fn(&[RispExp]) -> Result), 18 | Lambda(RispLambda), 19 | } 20 | 21 | #[derive(Clone)] 22 | struct RispLambda { 23 | params_exp: Rc, 24 | body_exp: Rc, 25 | } 26 | 27 | impl fmt::Display for RispExp { 28 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 29 | let str = match self { 30 | RispExp::Bool(a) => a.to_string(), 31 | RispExp::Symbol(s) => s.clone(), 32 | RispExp::Number(n) => n.to_string(), 33 | RispExp::List(list) => { 34 | let xs: Vec = list 35 | .iter() 36 | .map(|x| x.to_string()) 37 | .collect(); 38 | format!("({})", xs.join(",")) 39 | }, 40 | RispExp::Func(_) => "Function {}".to_string(), 41 | RispExp::Lambda(_) => "Lambda {}".to_string(), 42 | }; 43 | 44 | write!(f, "{}", str) 45 | } 46 | } 47 | 48 | 49 | #[derive(Debug)] 50 | enum RispErr { 51 | Reason(String), 52 | } 53 | 54 | #[derive(Clone)] 55 | struct RispEnv<'a> { 56 | data: HashMap, 57 | outer: Option<&'a RispEnv<'a>>, 58 | } 59 | 60 | /* 61 | Parse 62 | */ 63 | 64 | fn tokenize(expr: String) -> Vec { 65 | expr 66 | .replace("(", " ( ") 67 | .replace(")", " ) ") 68 | .split_whitespace() 69 | .map(|x| x.to_string()) 70 | .collect() 71 | } 72 | 73 | fn parse<'a>(tokens: &'a [String]) -> Result<(RispExp, &'a [String]), RispErr> { 74 | let (token, rest) = tokens.split_first() 75 | .ok_or( 76 | RispErr::Reason("could not get token".to_string()) 77 | )?; 78 | match &token[..] { 79 | "(" => read_seq(rest), 80 | ")" => Err(RispErr::Reason("unexpected `)`".to_string())), 81 | _ => Ok((parse_atom(token), rest)), 82 | } 83 | } 84 | 85 | fn read_seq<'a>(tokens: &'a [String]) -> Result<(RispExp, &'a [String]), RispErr> { 86 | let mut res: Vec = vec![]; 87 | let mut xs = tokens; 88 | loop { 89 | let (next_token, rest) = xs 90 | .split_first() 91 | .ok_or(RispErr::Reason("could not find closing `)`".to_string())) 92 | ?; 93 | if next_token == ")" { 94 | return Ok((RispExp::List(res), rest)) // skip `)`, head to the token after 95 | } 96 | let (exp, new_xs) = parse(&xs)?; 97 | res.push(exp); 98 | xs = new_xs; 99 | } 100 | } 101 | 102 | fn parse_atom(token: &str) -> RispExp { 103 | match token.as_ref() { 104 | "true" => RispExp::Bool(true), 105 | "false" => RispExp::Bool(false), 106 | _ => { 107 | let potential_float: Result = token.parse(); 108 | match potential_float { 109 | Ok(v) => RispExp::Number(v), 110 | Err(_) => RispExp::Symbol(token.to_string().clone()) 111 | } 112 | } 113 | } 114 | } 115 | 116 | /* 117 | Env 118 | */ 119 | 120 | macro_rules! ensure_tonicity { 121 | ($check_fn:expr) => {{ 122 | |args: &[RispExp]| -> Result { 123 | let floats = parse_list_of_floats(args)?; 124 | let first = floats.first().ok_or(RispErr::Reason("expected at least one number".to_string()))?; 125 | let rest = &floats[1..]; 126 | fn f (prev: &f64, xs: &[f64]) -> bool { 127 | match xs.first() { 128 | Some(x) => $check_fn(prev, x) && f(x, &xs[1..]), 129 | None => true, 130 | } 131 | }; 132 | Ok(RispExp::Bool(f(first, rest))) 133 | } 134 | }}; 135 | } 136 | 137 | fn default_env<'a>() -> RispEnv<'a> { 138 | let mut data: HashMap = HashMap::new(); 139 | data.insert( 140 | "+".to_string(), 141 | RispExp::Func( 142 | |args: &[RispExp]| -> Result { 143 | let sum = parse_list_of_floats(args)?.iter().fold(0.0, |sum, a| sum + a); 144 | 145 | Ok(RispExp::Number(sum)) 146 | } 147 | ) 148 | ); 149 | data.insert( 150 | "-".to_string(), 151 | RispExp::Func( 152 | |args: &[RispExp]| -> Result { 153 | let floats = parse_list_of_floats(args)?; 154 | let first = *floats.first().ok_or(RispErr::Reason("expected at least one number".to_string()))?; 155 | let sum_of_rest = floats[1..].iter().fold(0.0, |sum, a| sum + a); 156 | 157 | Ok(RispExp::Number(first - sum_of_rest)) 158 | } 159 | ) 160 | ); 161 | data.insert( 162 | "=".to_string(), 163 | RispExp::Func(ensure_tonicity!(|a, b| a == b)) 164 | ); 165 | data.insert( 166 | ">".to_string(), 167 | RispExp::Func(ensure_tonicity!(|a, b| a > b)) 168 | ); 169 | data.insert( 170 | ">=".to_string(), 171 | RispExp::Func(ensure_tonicity!(|a, b| a >= b)) 172 | ); 173 | data.insert( 174 | "<".to_string(), 175 | RispExp::Func(ensure_tonicity!(|a, b| a < b)) 176 | ); 177 | data.insert( 178 | "<=".to_string(), 179 | RispExp::Func(ensure_tonicity!(|a, b| a <= b)) 180 | ); 181 | 182 | RispEnv {data, outer: None} 183 | } 184 | 185 | fn parse_list_of_floats(args: &[RispExp]) -> Result, RispErr> { 186 | args 187 | .iter() 188 | .map(|x| parse_single_float(x)) 189 | .collect() 190 | } 191 | 192 | fn parse_single_float(exp: &RispExp) -> Result { 193 | match exp { 194 | RispExp::Number(num) => Ok(*num), 195 | _ => Err(RispErr::Reason("expected a number".to_string())), 196 | } 197 | } 198 | 199 | /* 200 | Eval 201 | */ 202 | 203 | fn eval_if_args(arg_forms: &[RispExp], env: &mut RispEnv) -> Result { 204 | let test_form = arg_forms.first().ok_or( 205 | RispErr::Reason( 206 | "expected test form".to_string(), 207 | ) 208 | )?; 209 | let test_eval = eval(test_form, env)?; 210 | match test_eval { 211 | RispExp::Bool(b) => { 212 | let form_idx = if b { 1 } else { 2 }; 213 | let res_form = arg_forms.get(form_idx) 214 | .ok_or(RispErr::Reason( 215 | format!("expected form idx={}", form_idx) 216 | ))?; 217 | let res_eval = eval(res_form, env); 218 | 219 | res_eval 220 | }, 221 | _ => Err( 222 | RispErr::Reason(format!("unexpected test form='{}'", test_form.to_string())) 223 | ) 224 | } 225 | } 226 | 227 | fn eval_def_args(arg_forms: &[RispExp], env: &mut RispEnv) -> Result { 228 | let first_form = arg_forms.first().ok_or( 229 | RispErr::Reason( 230 | "expected first form".to_string(), 231 | ) 232 | )?; 233 | let first_str = match first_form { 234 | RispExp::Symbol(s) => Ok(s.clone()), 235 | _ => Err(RispErr::Reason( 236 | "expected first form to be a symbol".to_string(), 237 | )) 238 | }?; 239 | let second_form = arg_forms.get(1).ok_or( 240 | RispErr::Reason( 241 | "expected second form".to_string(), 242 | ) 243 | )?; 244 | if arg_forms.len() > 2 { 245 | return Err( 246 | RispErr::Reason( 247 | "def can only have two forms ".to_string(), 248 | ) 249 | ) 250 | } 251 | let second_eval = eval(second_form, env)?; 252 | env.data.insert(first_str, second_eval); 253 | 254 | Ok(first_form.clone()) 255 | } 256 | 257 | 258 | fn eval_lambda_args(arg_forms: &[RispExp]) -> Result { 259 | let params_exp = arg_forms.first().ok_or( 260 | RispErr::Reason( 261 | "expected args form".to_string(), 262 | ) 263 | )?; 264 | let body_exp = arg_forms.get(1).ok_or( 265 | RispErr::Reason( 266 | "expected second form".to_string(), 267 | ) 268 | )?; 269 | if arg_forms.len() > 2 { 270 | return Err( 271 | RispErr::Reason( 272 | "fn definition can only have two forms ".to_string(), 273 | ) 274 | ) 275 | } 276 | 277 | Ok( 278 | RispExp::Lambda( 279 | RispLambda { 280 | body_exp: Rc::new(body_exp.clone()), 281 | params_exp: Rc::new(params_exp.clone()), 282 | } 283 | ) 284 | ) 285 | } 286 | 287 | 288 | fn eval_built_in_form( 289 | exp: &RispExp, arg_forms: &[RispExp], env: &mut RispEnv 290 | ) -> Option> { 291 | match exp { 292 | RispExp::Symbol(s) => 293 | match s.as_ref() { 294 | "if" => Some(eval_if_args(arg_forms, env)), 295 | "def" => Some(eval_def_args(arg_forms, env)), 296 | "fn" => Some(eval_lambda_args(arg_forms)), 297 | _ => None, 298 | } 299 | , 300 | _ => None, 301 | } 302 | } 303 | 304 | fn env_get(k: &str, env: &RispEnv) -> Option { 305 | match env.data.get(k) { 306 | Some(exp) => Some(exp.clone()), 307 | None => { 308 | match &env.outer { 309 | Some(outer_env) => env_get(k, &outer_env), 310 | None => None 311 | } 312 | } 313 | } 314 | } 315 | 316 | fn parse_list_of_symbol_strings(form: Rc) -> Result, RispErr> { 317 | let list = match form.as_ref() { 318 | RispExp::List(s) => Ok(s.clone()), 319 | _ => Err(RispErr::Reason( 320 | "expected args form to be a list".to_string(), 321 | )) 322 | }?; 323 | list 324 | .iter() 325 | .map( 326 | |x| { 327 | match x { 328 | RispExp::Symbol(s) => Ok(s.clone()), 329 | _ => Err(RispErr::Reason( 330 | "expected symbols in the argument list".to_string(), 331 | )) 332 | } 333 | } 334 | ).collect() 335 | } 336 | 337 | fn env_for_lambda<'a>( 338 | params: Rc, 339 | arg_forms: &[RispExp], 340 | outer_env: &'a mut RispEnv, 341 | ) -> Result, RispErr> { 342 | let ks = parse_list_of_symbol_strings(params)?; 343 | if ks.len() != arg_forms.len() { 344 | return Err( 345 | RispErr::Reason( 346 | format!("expected {} arguments, got {}", ks.len(), arg_forms.len()) 347 | ) 348 | ); 349 | } 350 | let vs = eval_forms(arg_forms, outer_env)?; 351 | let mut data: HashMap = HashMap::new(); 352 | for (k, v) in ks.iter().zip(vs.iter()) { 353 | data.insert(k.clone(), v.clone()); 354 | } 355 | Ok( 356 | RispEnv { 357 | data, 358 | outer: Some(outer_env), 359 | } 360 | ) 361 | } 362 | 363 | fn eval_forms(arg_forms: &[RispExp], env: &mut RispEnv) -> Result, RispErr> { 364 | arg_forms 365 | .iter() 366 | .map(|x| eval(x, env)) 367 | .collect() 368 | } 369 | 370 | fn eval(exp: &RispExp, env: &mut RispEnv) -> Result { 371 | match exp { 372 | RispExp::Symbol(k) => 373 | env_get(k, env) 374 | .ok_or( 375 | RispErr::Reason( 376 | format!("unexpected symbol k='{}'", k) 377 | ) 378 | ) 379 | , 380 | RispExp::Bool(_a) => Ok(exp.clone()), 381 | RispExp::Number(_a) => Ok(exp.clone()), 382 | 383 | RispExp::List(list) => { 384 | let first_form = list 385 | .first() 386 | .ok_or(RispErr::Reason("expected a non-empty list".to_string()))?; 387 | let arg_forms = &list[1..]; 388 | match eval_built_in_form(first_form, arg_forms, env) { 389 | Some(res) => res, 390 | None => { 391 | let first_eval = eval(first_form, env)?; 392 | match first_eval { 393 | RispExp::Func(f) => { 394 | f(&eval_forms(arg_forms, env)?) 395 | }, 396 | RispExp::Lambda(lambda) => { 397 | let new_env = &mut env_for_lambda(lambda.params_exp, arg_forms, env)?; 398 | eval(&lambda.body_exp, new_env) 399 | }, 400 | _ => Err( 401 | RispErr::Reason("first form must be a function".to_string()) 402 | ), 403 | } 404 | } 405 | } 406 | }, 407 | RispExp::Func(_) => Err(RispErr::Reason("unexpected form".to_string())), 408 | RispExp::Lambda(_) => Err(RispErr::Reason("unexpected form".to_string())), 409 | } 410 | } 411 | 412 | /* 413 | Repl 414 | */ 415 | 416 | fn parse_eval(expr: String, env: &mut RispEnv) -> Result { 417 | let (parsed_exp, _) = parse(&tokenize(expr))?; 418 | let evaled_exp = eval(&parsed_exp, env)?; 419 | 420 | Ok(evaled_exp) 421 | } 422 | 423 | fn slurp_expr() -> String { 424 | let mut expr = String::new(); 425 | 426 | io::stdin().read_line(&mut expr) 427 | .expect("Failed to read line"); 428 | 429 | expr 430 | } 431 | 432 | fn main() { 433 | let env = &mut default_env(); 434 | loop { 435 | println!("risp >"); 436 | let expr = slurp_expr(); 437 | match parse_eval(expr, env) { 438 | Ok(res) => println!("// 🔥 => {}", res), 439 | Err(e) => match e { 440 | RispErr::Reason(msg) => println!("// 🙀 => {}", msg), 441 | }, 442 | } 443 | } 444 | } --------------------------------------------------------------------------------