add evaluation for terms

Co-authored-by: rvcas <x@rvcas.dev>
This commit is contained in:
Kasey White 2022-07-06 02:11:46 -04:00 committed by Kasey White
parent 795d9ee028
commit 0bf93e59b5
4 changed files with 220 additions and 168 deletions

View File

@ -1,150 +0,0 @@
use flat_rs::de;
use crate::{
ast::{Constant, NamedDeBruijn, Term},
builtins::DefaultFunction,
};
#[repr(u8)]
#[derive(Debug, Clone, PartialEq, Copy)]
enum StepKind {
BConst = 0,
BVar = 1,
BLamAbs = 2,
BApply = 3,
BDelay = 4,
BForce = 5,
BBuiltin = 6,
}
impl TryFrom<u8> for StepKind {
type Error = de::Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(StepKind::BConst),
1 => Ok(StepKind::BVar),
2 => Ok(StepKind::BLamAbs),
3 => Ok(StepKind::BApply),
4 => Ok(StepKind::BDelay),
5 => Ok(StepKind::BForce),
6 => Ok(StepKind::BBuiltin),
v => Err(de::Error::Message(format!(
"Default Function not found - {}",
v
))),
}
}
}
enum ExBudgetCategory {
BStep(StepKind),
BBuiltinApp(DefaultFunction),
BStartup,
}
/// Can be negative
#[derive(Debug, Clone, PartialEq)]
struct ExBudget {
mem: i32,
cpu: i32,
}
impl std::ops::Mul<i32> for ExBudget {
type Output = ExBudget;
fn mul(self, rhs: i32) -> ExBudget {
ExBudget {
mem: self.mem * rhs,
cpu: self.cpu * rhs,
}
}
}
enum CekValue {
VCon(Constant),
VDelay(Term<NamedDeBruijn>, Vec<CekValue>),
VLamAbs(NamedDeBruijn, Term<NamedDeBruijn>, Vec<CekValue>),
VBuiltin(
DefaultFunction,
Term<NamedDeBruijn>,
// Need to figure out run time stuff
// BuiltinRuntime (CekValue uni fun)
),
}
enum Context {
FrameApplyFun(Term<NamedDeBruijn>, Term<NamedDeBruijn>),
FrameApplyArg(Vec<CekValue>, Term<NamedDeBruijn>, Box<Context>),
FrameForce(Box<Context>),
NoFrame,
}
// For now let's just start with running cek on term with generic params
fn run_cek_debruijn(term: Term<NamedDeBruijn>) -> (Term<NamedDeBruijn>, usize, Vec<String>) {
//paramerterize this
let mut initial_budget = ExBudget {
mem: 1000,
cpu: 1000,
};
let startup_budget = ExBudget { mem: 10, cpu: 10 };
spend_budget_cek(
&mut initial_budget,
ExBudgetCategory::BStartup,
startup_budget,
);
let evaluation = enter_compute_cek(Context::NoFrame, Vec::new(), term, &mut initial_budget);
todo!()
}
fn enter_compute_cek(
frame: Context,
env: Vec<CekValue>,
term: Term<NamedDeBruijn>,
current_budget: &mut ExBudget,
) -> (Term<NamedDeBruijn>, usize, Vec<u32>) {
//enter compute
compute_cek(&mut Vec::with_capacity(8), frame, env, term, current_budget)
}
fn compute_cek(
unbudgeted_steps: &mut Vec<u32>,
frame: Context,
env: Vec<CekValue>,
term: Term<NamedDeBruijn>,
current_budget: &mut ExBudget,
) -> (Term<NamedDeBruijn>, usize, Vec<u32>) {
//TODO: parameterize slippage
let slippage = 200;
match (term) {
a @ Term::Var(_) => {
unbudgeted_steps[2] += 1;
unbudgeted_steps[7] += 1;
if unbudgeted_steps[7] >= slippage {
spend_accumulated_budget_cek(unbudgeted_steps, current_budget);
}
todo!()
}
Term::Delay(_) => todo!(),
Term::Lambda {
parameter_name,
body,
} => todo!(),
Term::Apply { function, argument } => todo!(),
Term::Constant(_) => todo!(),
Term::Force(_) => todo!(),
Term::Error => todo!(),
Term::Builtin(_) => todo!(),
}
}
fn spend_accumulated_budget_cek(unbudgeted_steps: &mut Vec<u32>, current_budget: &mut ExBudget) {
//only spend each step kind and not total which is index
for i in 0..unbudgeted_steps.len() - 1 {
let step = StepKind::try_from(i as u8).unwrap();
spend_budget_cek(
current_budget,
ExBudgetCategory::BStep(step),
get_cost_by_step(step) * unbudgeted_steps[i] as i32,
);
}
}

View File

@ -2,7 +2,6 @@ pub mod ast;
pub mod builtins; pub mod builtins;
mod debruijn; mod debruijn;
mod flat; mod flat;
pub mod interpreter;
pub mod machine; pub mod machine;
pub mod parser; pub mod parser;
mod pretty; mod pretty;

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
ast::{Constant, DeBruijn, Term}, ast::{Constant, NamedDeBruijn, Term},
builtins::DefaultFunction, builtins::DefaultFunction,
}; };
@ -13,6 +13,7 @@ pub struct Machine {
frames: Vec<Context>, frames: Vec<Context>,
slippage: u32, slippage: u32,
env: Vec<Value>, env: Vec<Value>,
unbudgeted_steps: Vec<u32>,
} }
impl Machine { impl Machine {
@ -23,23 +24,172 @@ impl Machine {
slippage, slippage,
frames: vec![], frames: vec![],
env: vec![], env: vec![],
unbudgeted_steps: vec![0; 8],
} }
} }
pub fn run( pub fn run(
&mut self, &mut self,
term: &Term<DeBruijn>, term: &Term<NamedDeBruijn>,
) -> Result<(Term<DeBruijn>, usize, Vec<String>), Error> { ) -> Result<(Term<NamedDeBruijn>, usize, Vec<String>), Error> {
let startup_budget = self.costs.get(StepKind::StartUp); let startup_budget = self.costs.get(StepKind::StartUp);
self.spend_budget(startup_budget)?; self.spend_budget(startup_budget)?;
self.push_frame(Context::NoFrame); self.push_frame(Context::NoFrame);
self.enter_compute(term) self.enter_compute(term)?;
todo!()
} }
fn enter_compute(&mut self, term: &Term<DeBruijn>) {} fn enter_compute(&mut self, term: &Term<NamedDeBruijn>) -> Result<Term<NamedDeBruijn>, Error> {
match term {
Term::Var(name) => {
self.unbudgeted_steps[1] += 1;
self.unbudgeted_steps[7] += 1;
if self.unbudgeted_steps[7] >= self.slippage {
self.spend_unbudgeted_steps()?;
}
let val = self.lookup_var(name.clone())?;
self.return_compute(val)
}
Term::Delay(body) => {
self.unbudgeted_steps[4] += 1;
self.unbudgeted_steps[7] += 1;
if self.unbudgeted_steps[7] >= self.slippage {
self.spend_unbudgeted_steps()?;
}
self.return_compute(Value::Delay(*body.clone()))
}
Term::Lambda {
parameter_name,
body,
} => {
self.unbudgeted_steps[2] += 1;
self.unbudgeted_steps[7] += 1;
if self.unbudgeted_steps[7] >= self.slippage {
self.spend_unbudgeted_steps()?;
}
self.return_compute(Value::Lambda {
parameter_name: parameter_name.clone(),
body: *body.clone(),
})
}
Term::Apply { function, argument } => {
self.unbudgeted_steps[3] += 1;
self.unbudgeted_steps[7] += 1;
if self.unbudgeted_steps[7] >= self.slippage {
self.spend_unbudgeted_steps()?;
}
self.push_frame(Context::FrameApplyArg(*argument.clone()));
self.enter_compute(function)
}
Term::Constant(x) => {
self.unbudgeted_steps[0] += 1;
self.unbudgeted_steps[7] += 1;
if self.unbudgeted_steps[7] >= self.slippage {
self.spend_unbudgeted_steps()?;
}
self.return_compute(Value::Con(x.clone()))
}
Term::Force(body) => {
self.unbudgeted_steps[5] += 1;
self.unbudgeted_steps[7] += 1;
if self.unbudgeted_steps[7] >= self.slippage {
self.spend_unbudgeted_steps()?;
}
self.push_frame(Context::FrameForce);
self.enter_compute(body)
}
Term::Error => Err(Error::EvaluationFailure),
Term::Builtin(_) => todo!(),
}
}
fn return_compute(&mut self, value: Value) -> Result<Term<NamedDeBruijn>, Error> {
let frame = self.frames.last().cloned().unwrap();
match frame {
Context::FrameApplyFun(function) => {
self.pop_frame();
self.apply_evaluate(function, value)
}
Context::FrameApplyArg(arg) => {
self.pop_frame();
self.push_frame(Context::FrameApplyFun(value));
self.enter_compute(&arg)
}
Context::FrameForce => {
self.pop_frame();
self.force_evaluate(value)
}
Context::NoFrame => {
if self.unbudgeted_steps[7] > 0 {
self.spend_unbudgeted_steps()?;
}
let term = self.discharge_value(value);
Ok(term)
}
}
}
fn discharge_value(&mut self, value: Value) -> Term<NamedDeBruijn> {
match value {
Value::Con(x) => Term::Constant(x),
Value::Builtin(_, t) => t,
Value::Delay(_) => todo!(),
Value::Lambda {
parameter_name,
body,
} => self.discharge_value_env(Term::Lambda {
parameter_name: NamedDeBruijn {
text: parameter_name.text,
index: 0.into(),
},
body: Box::new(body),
}),
}
}
fn discharge_value_env(&mut self, term: Term<NamedDeBruijn>) -> Term<NamedDeBruijn> {
fn rec(i: u32, t: Term<NamedDeBruijn>) -> Term<NamedDeBruijn> {
match t {
Term::Var(x) => todo!(),
Term::Lambda {
parameter_name,
body,
} => Term::Lambda {
parameter_name,
body: Box::new(rec(i + 1, *body)),
},
Term::Apply { function, argument } => Term::Apply {
function: Box::new(rec(i, *function)),
argument: Box::new(rec(i, *argument)),
},
Term::Delay(x) => Term::Delay(Box::new(rec(i, *x))),
Term::Force(x) => Term::Force(Box::new(rec(i, *x))),
rest => rest,
}
}
rec(0, term)
}
fn force_evaluate(&mut self, value: Value) -> Result<Term<NamedDeBruijn>, Error> {
match value {
Value::Delay(body) => self.enter_compute(&body),
Value::Builtin(_, _) => todo!(),
rest => Err(Error::NonPolymorphicInstantiation(rest)),
}
}
fn apply_evaluate(
&mut self,
function: Value,
argument: Value,
) -> Result<Term<NamedDeBruijn>, Error> {
todo!()
}
fn spend_budget(&mut self, spend_budget: ExBudget) -> Result<(), Error> { fn spend_budget(&mut self, spend_budget: ExBudget) -> Result<(), Error> {
self.ex_budget.mem -= spend_budget.mem; self.ex_budget.mem -= spend_budget.mem;
@ -55,30 +205,56 @@ impl Machine {
fn push_frame(&mut self, frame: Context) { fn push_frame(&mut self, frame: Context) {
self.frames.push(frame); self.frames.push(frame);
} }
fn pop_frame(&mut self) {
self.frames.pop();
} }
fn lookup_var(&mut self, name: NamedDeBruijn) -> Result<Value, Error> {
self.env
.get::<usize>(name.index.into())
.cloned()
.ok_or(Error::OpenTermEvaluated(Term::Var(name)))
}
fn spend_unbudgeted_steps(&mut self) -> Result<(), Error> {
for i in 0..self.unbudgeted_steps.len() - 1 {
let mut unspent_step_budget = self.costs.get(StepKind::try_from(i as u8)?);
unspent_step_budget.occurence(self.unbudgeted_steps[i] as i32);
self.spend_budget(unspent_step_budget)?;
}
self.unbudgeted_steps = vec![0; 8];
Ok(())
}
}
#[derive(Clone)]
enum Context { enum Context {
FrameApplyFun(Term<DeBruijn>, Term<DeBruijn>), FrameApplyFun(Value),
FrameApplyArg(Vec<Value>, Term<DeBruijn>, Box<Context>), FrameApplyArg(Term<NamedDeBruijn>),
FrameForce(Box<Context>), FrameForce,
NoFrame, NoFrame,
} }
enum Value { #[derive(Clone, Debug)]
pub enum Value {
Con(Constant), Con(Constant),
Delay(Term<DeBruijn>, Vec<Value>), Delay(Term<NamedDeBruijn>),
Lambda(DeBruijn, Term<DeBruijn>, Vec<Value>), Lambda {
parameter_name: NamedDeBruijn,
body: Term<NamedDeBruijn>,
},
Builtin( Builtin(
DefaultFunction, DefaultFunction,
Term<DeBruijn>, Term<NamedDeBruijn>,
// Need to figure out run time stuff // Need to figure out run time stuff
// BuiltinRuntime (CekValue uni fun) // BuiltinRuntime (CekValue uni fun)
), ),
} }
/// Can be negative /// Can be negative
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Copy)]
struct ExBudget { pub struct ExBudget {
mem: i32, mem: i32,
cpu: i32, cpu: i32,
} }
@ -90,7 +266,7 @@ impl ExBudget {
} }
} }
enum StepKind { pub enum StepKind {
Constant, Constant,
Var, Var,
Lambda, Lambda,
@ -101,9 +277,26 @@ enum StepKind {
StartUp, StartUp,
} }
impl TryFrom<u8> for StepKind {
type Error = error::Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(StepKind::Constant),
1 => Ok(StepKind::Var),
2 => Ok(StepKind::Lambda),
3 => Ok(StepKind::Apply),
4 => Ok(StepKind::Delay),
5 => Ok(StepKind::Force),
6 => Ok(StepKind::Builtin),
v => Err(error::Error::InvalidStepKind(v)),
}
}
}
/// There's no entry for Error since we'll be exiting anyway; also, what would /// There's no entry for Error since we'll be exiting anyway; also, what would
/// happen if calling 'Error' caused the budget to be exceeded? /// happen if calling 'Error' caused the budget to be exceeded?
struct Costs { pub struct Costs {
startup: ExBudget, startup: ExBudget,
var: ExBudget, var: ExBudget,
constant: ExBudget, constant: ExBudget,

View File

@ -1,9 +1,19 @@
use thiserror::Error; use thiserror::Error;
use super::ExBudget; use crate::ast::{NamedDeBruijn, Term};
use super::{ExBudget, Value};
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum Error { pub enum Error {
#[error("Over budget mem: {} & cpu: {}", .0.mem, .0.cpu)] #[error("Over budget mem: {} & cpu: {}", .0.mem, .0.cpu)]
OutOfExError(ExBudget), OutOfExError(ExBudget),
#[error("Invalid Stepkind: {0}")]
InvalidStepKind(u8),
#[error("Cannot evaluate an open term: {0:#?}")]
OpenTermEvaluated(Term<NamedDeBruijn>),
#[error("The provided Plutus code called 'error'.")]
EvaluationFailure,
#[error("Attempted to instantiate a non-polymorphic term: {0:#?}")]
NonPolymorphicInstantiation(Value),
} }