Created
May 8, 2018 08:53
-
-
Save eira-fransham/dd119cea579f6076bc0edbf1754a88f9 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
#[macro_use] | |
extern crate combine; | |
use std::collections::HashMap; | |
#[derive(Debug, Clone)] | |
enum Ast { | |
Lit(Value), | |
Variable(String), | |
Call(Box<Ast>, Vec<Ast>), | |
Define(String, Box<Ast>), | |
} | |
#[derive(Debug, Clone)] | |
enum Value { | |
Void, | |
Int(u64), | |
Function(Vec<String>, Vec<Ast>), | |
InbuiltFunc(fn(Vec<Value>) -> Value), | |
} | |
fn eval(program: Ast, variables: &mut HashMap<String, Value>) -> Value { | |
use self::Ast::*; | |
use self::Value::*; | |
match program { | |
Lit(val) => val, | |
Variable(name) => match variables.get(&name) { | |
Some(v) => v.clone(), | |
_ => panic!("Variable does not exist: {}", &name), | |
}, | |
Call(func, arguments) => { | |
let func = eval(*func, variables); | |
match func { | |
Function(args, body) => { | |
// Start a new scope, so all variables defined in the body of the | |
// function don't leak into the surrounding scope. | |
let mut new_scope = variables.clone(); | |
if arguments.len() != args.len() { | |
println!("Called function with incorrect number of arguments (expected {}, got {})", args.len(), arguments.len()); | |
} | |
for (name, val) in args.into_iter().zip(arguments) { | |
let val = eval(val, variables); | |
new_scope.insert(name, val); | |
} | |
let mut out = Void; | |
for stmt in body { | |
out = eval(stmt, &mut new_scope); | |
} | |
out | |
} | |
InbuiltFunc(func) => func( | |
arguments | |
.into_iter() | |
.map(|ast| eval(ast, variables)) | |
.collect(), | |
), | |
_ => panic!("Attempted to call a non-function"), | |
} | |
} | |
Define(name, value) => { | |
let value = eval(*value, variables); | |
variables.insert(name, value); | |
Void | |
} | |
} | |
} | |
parser! { | |
fn expr[I]()(I) -> Ast where [I: combine::Stream<Item = char>] { | |
use combine::*; | |
use combine::parser::char::*; | |
macro_rules! white { | |
($prs:expr) => { | |
between( | |
skip_many(satisfy(char::is_whitespace)), | |
skip_many(satisfy(char::is_whitespace)), | |
$prs | |
) | |
} | |
} | |
let lambda = char('\\'); | |
let eq = char('='); | |
let ident = || white!(many1::<String, _>(letter())); | |
let function = ( | |
white!(lambda), | |
white!(between(char('('), char(')'), many::<Vec<_>, _>(ident()))), | |
many::<Vec<_>, _>(expr()), | |
).map(|(_, a, b)| Ast::Lit(::Value::Function(a, b))); | |
let define = ( | |
white!(eq), | |
ident(), | |
expr(), | |
).map(|(_, a, b)| Ast::Define(a, Box::new(b))); | |
let lit_num = many1::<String, _>(digit()) | |
.map(|i| Ast::Lit(::Value::Int(i.parse().expect("Parsing integer failed")))); | |
let call = ( | |
expr(), | |
many1(expr()), | |
).map(|(func, args)| { | |
Ast::Call(Box::new(func), args) | |
}); | |
white!( | |
choice!( | |
lit_num, | |
ident().map(Ast::Variable), | |
between( | |
char('('), | |
char(')'), | |
choice!( | |
function, | |
define, | |
call | |
) | |
) | |
) | |
) | |
} | |
} | |
fn add(variables: Vec<Value>) -> Value { | |
let mut out = 0u64; | |
for v in variables { | |
match v { | |
Value::Int(i) => out += i, | |
_ => println!("Tried to add a non-int"), | |
} | |
} | |
Value::Int(out) | |
} | |
fn main() { | |
use self::Ast::*; | |
use self::Value::*; | |
use combine::stream::state::State; | |
use combine::{many1, Parser}; | |
let mut variables = HashMap::<String, Value>::new(); | |
variables.insert("add".into(), InbuiltFunc(add)); | |
let program_text = r" | |
(= increment (\(a) (add a 1))) | |
(increment 2) | |
"; | |
let (program, _rest) = many1::<Vec<_>, _>(expr()) | |
.easy_parse(State::new(program_text)) | |
.unwrap(); | |
for i in program { | |
println!("{:?}", eval(i, &mut variables)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment