Skip to content

Instantly share code, notes, and snippets.

@eira-fransham
Created May 8, 2018 08:53
Show Gist options
  • Save eira-fransham/dd119cea579f6076bc0edbf1754a88f9 to your computer and use it in GitHub Desktop.
Save eira-fransham/dd119cea579f6076bc0edbf1754a88f9 to your computer and use it in GitHub Desktop.
#[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