feat: 3 new error cases and more generic pretty printing

This commit is contained in:
rvcas 2022-07-15 23:26:57 -04:00 committed by Kasey White
parent 598c5364fe
commit f332dfeb38
10 changed files with 118 additions and 55 deletions

View File

@ -1,4 +1,7 @@
(program (program
1.0.0 1.0.0
[ (builtin ifThenElse) (con bool True) (con integer 1) (con string "yo") ] [
[ [ (force (builtin ifThenElse)) (con integer 2) ] (con integer 1) ]
(con string "yo")
]
) )

View File

@ -32,8 +32,11 @@ pub enum UplcCommand {
out: Option<String>, out: Option<String>,
}, },
/// Format an Untyped Plutus Core program /// Format an Untyped Plutus Core program
Fmt { input: PathBuf }, Fmt {
input: PathBuf,
#[clap(short, long)]
print: bool,
},
/// Evaluate an Untyped Plutus Core program /// Evaluate an Untyped Plutus Core program
Eval { Eval {
input: PathBuf, input: PathBuf,

View File

@ -1,4 +1,4 @@
use std::fs; use std::{fmt::Write as _, fs};
use uplc::{ use uplc::{
ast::{DeBruijn, FakeNamedDeBruijn, Name, NamedDeBruijn, Program, Term}, ast::{DeBruijn, FakeNamedDeBruijn, Name, NamedDeBruijn, Program, Term},
@ -27,7 +27,7 @@ fn main() -> anyhow::Result<()> {
let mut output = String::new(); let mut output = String::new();
for (i, byte) in bytes.iter().enumerate() { for (i, byte) in bytes.iter().enumerate() {
output.push_str(&format!("{:08b}", byte)); let _ = write!(output, "{:08b}", byte);
if (i + 1) % 4 == 0 { if (i + 1) % 4 == 0 {
output.push('\n'); output.push('\n');
@ -47,15 +47,19 @@ fn main() -> anyhow::Result<()> {
fs::write(&out_name, &bytes)?; fs::write(&out_name, &bytes)?;
} }
} }
UplcCommand::Fmt { input } => { UplcCommand::Fmt { input, print } => {
let code = std::fs::read_to_string(&input)?; let code = std::fs::read_to_string(&input)?;
let program = parser::program(&code)?; let program = parser::program(&code)?;
let pretty = program.to_pretty(); let pretty = program.to_pretty();
if print {
println!("{}", pretty);
} else {
fs::write(&input, pretty)?; fs::write(&input, pretty)?;
} }
}
UplcCommand::Unflat { input, print, out } => { UplcCommand::Unflat { input, print, out } => {
let bytes = std::fs::read(&input)?; let bytes = std::fs::read(&input)?;
@ -105,7 +109,7 @@ fn main() -> anyhow::Result<()> {
} }
} }
println!("Costs - memory: {} & cpu: {}", cost.mem, cost.cpu); println!("\nCosts - memory: {} & cpu: {}", cost.mem, cost.cpu);
} }
}, },
} }

View File

@ -162,7 +162,7 @@ impl PartialEq for NamedDeBruijn {
/// It allows for injecting fake textual names while also using Debruijn for decoding /// It allows for injecting fake textual names while also using Debruijn for decoding
/// without having to loop through twice. /// without having to loop through twice.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct FakeNamedDeBruijn(NamedDeBruijn); pub struct FakeNamedDeBruijn(pub NamedDeBruijn);
impl From<DeBruijn> for FakeNamedDeBruijn { impl From<DeBruijn> for FakeNamedDeBruijn {
fn from(d: DeBruijn) -> Self { fn from(d: DeBruijn) -> Self {

View File

@ -18,6 +18,7 @@ const TERM_TAG_WIDTH: u32 = 4;
pub trait Binder<'b>: Encode + Decode<'b> { pub trait Binder<'b>: Encode + Decode<'b> {
fn binder_encode(&self, e: &mut Encoder) -> Result<(), en::Error>; fn binder_encode(&self, e: &mut Encoder) -> Result<(), en::Error>;
fn binder_decode(d: &mut Decoder) -> Result<Self, de::Error>; fn binder_decode(d: &mut Decoder) -> Result<Self, de::Error>;
fn text(&self) -> &str;
} }
impl<'b, T> Flat<'b> for Program<T> where T: Binder<'b> + Debug {} impl<'b, T> Flat<'b> for Program<T> where T: Binder<'b> + Debug {}
@ -246,6 +247,10 @@ impl<'b> Binder<'b> for Name {
fn binder_decode(d: &mut Decoder) -> Result<Self, de::Error> { fn binder_decode(d: &mut Decoder) -> Result<Self, de::Error> {
Name::decode(d) Name::decode(d)
} }
fn text(&self) -> &str {
&self.text
}
} }
impl Encode for NamedDeBruijn { impl Encode for NamedDeBruijn {
@ -279,6 +284,10 @@ impl<'b> Binder<'b> for NamedDeBruijn {
index: DeBruijn::new(0), index: DeBruijn::new(0),
}) })
} }
fn text(&self) -> &str {
&self.text
}
} }
impl Encode for DeBruijn { impl Encode for DeBruijn {
@ -303,6 +312,10 @@ impl<'b> Binder<'b> for DeBruijn {
fn binder_decode(_d: &mut Decoder) -> Result<Self, de::Error> { fn binder_decode(_d: &mut Decoder) -> Result<Self, de::Error> {
Ok(DeBruijn::new(0)) Ok(DeBruijn::new(0))
} }
fn text(&self) -> &str {
"i"
}
} }
impl Encode for FakeNamedDeBruijn { impl Encode for FakeNamedDeBruijn {
@ -333,6 +346,10 @@ impl<'b> Binder<'b> for FakeNamedDeBruijn {
Ok(index.into()) Ok(index.into())
} }
fn text(&self) -> &str {
&self.0.text
}
} }
impl Encode for DefaultFunction { impl Encode for DefaultFunction {

View File

@ -10,7 +10,7 @@ mod runtime;
use cost_model::{ExBudget, StepKind}; use cost_model::{ExBudget, StepKind};
pub use error::Error; pub use error::Error;
use self::{cost_model::CostModel, runtime::BuiltinRuntime}; use self::{cost_model::CostModel, error::Type, runtime::BuiltinRuntime};
pub struct Machine { pub struct Machine {
costs: CostModel, costs: CostModel,
@ -198,7 +198,7 @@ impl Machine {
term, term,
mut runtime, mut runtime,
} => { } => {
let force_term = Term::Force(Box::new(dbg!(term))); let force_term = Term::Force(Box::new(term));
if runtime.needs_force() { if runtime.needs_force() {
runtime.consume_force(); runtime.consume_force();
@ -207,7 +207,7 @@ impl Machine {
self.return_compute(res) self.return_compute(res)
} else { } else {
todo!() Err(Error::BuiltinTermArgumentExpected(force_term))
} }
} }
rest => Err(Error::NonPolymorphicInstantiation(rest)), rest => Err(Error::NonPolymorphicInstantiation(rest)),
@ -245,7 +245,7 @@ impl Machine {
self.return_compute(res) self.return_compute(res)
} else { } else {
todo!() Err(Error::UnexpectedBuiltinTermArgument(t))
} }
} }
rest => Err(Error::NonFunctionalApplication(rest)), rest => Err(Error::NonFunctionalApplication(rest)),
@ -367,7 +367,7 @@ impl Value {
} else { } else {
//TODO //TODO
// std::mem::size_of( i.abs() // std::mem::size_of( i.abs()
todo!() 1
} }
} }
Constant::ByteString(b) => (((b.len() - 1) / 8) + 1) as i64, Constant::ByteString(b) => (((b.len() - 1) / 8) + 1) as i64,
@ -382,3 +382,21 @@ impl Value {
} }
} }
} }
impl From<&Value> for Type {
fn from(value: &Value) -> Self {
match value {
Value::Con(constant) => match constant {
Constant::Integer(_) => Type::Integer,
Constant::ByteString(_) => Type::ByteString,
Constant::String(_) => Type::String,
Constant::Char(_) => todo!(),
Constant::Unit => Type::Unit,
Constant::Bool(_) => Type::Bool,
},
Value::Delay(_) => todo!(),
Value::Lambda { .. } => todo!(),
Value::Builtin { .. } => todo!(),
}
}
}

View File

@ -1,3 +1,5 @@
use std::fmt::Display;
use thiserror::Error; use thiserror::Error;
use crate::ast::{NamedDeBruijn, Term}; use crate::ast::{NamedDeBruijn, Term};
@ -18,4 +20,31 @@ pub enum Error {
NonPolymorphicInstantiation(Value), NonPolymorphicInstantiation(Value),
#[error("Attempted to apply a non-function: {0:#?}")] #[error("Attempted to apply a non-function: {0:#?}")]
NonFunctionalApplication(Value), NonFunctionalApplication(Value),
#[error("Type mismatch expected '{0}' got '{1}'")]
TypeMismatch(Type, Type),
#[error("A builtin received a term argument when something else was expected:\n\n{}\n\nYou probably forgot to wrap the builtin with a force.", .0.to_pretty())]
UnexpectedBuiltinTermArgument(Term<NamedDeBruijn>),
#[error("A builtin expected a term argument, but something else was received:\n\n{}\n\nYou probably have an extra force wrapped around a builtin", .0.to_pretty())]
BuiltinTermArgumentExpected(Term<NamedDeBruijn>),
}
#[derive(Debug, Clone)]
pub enum Type {
Bool,
Integer,
String,
ByteString,
Unit,
}
impl Display for Type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Type::Bool => write!(f, "bool"),
Type::Integer => write!(f, "integer"),
Type::String => write!(f, "string"),
Type::ByteString => write!(f, "bytestring"),
Type::Unit => write!(f, "unit"),
}
}
} }

View File

@ -2,9 +2,8 @@ use crate::{ast::Constant, builtins::DefaultFunction};
use super::{ use super::{
cost_model::{BuiltinCosts, ExBudget}, cost_model::{BuiltinCosts, ExBudget},
// cost_model::{CostingFun, ExBudget}, error::Type,
Error, Error, Value,
Value,
}; };
//#[derive(std::cmp::PartialEq)] //#[derive(std::cmp::PartialEq)]
@ -13,12 +12,6 @@ use super::{
// Deferred, // Deferred,
//} //}
// pub struct BuiltinRuntimeOptions<T, G> {
// immediate_eval: T,
// deferred_eval: T,
// budget: fn(CostingFun<G>) -> fn(Vec<u32>) -> ExBudget,
// }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct BuiltinRuntime { pub struct BuiltinRuntime {
args: Vec<Value>, args: Vec<Value>,
@ -35,16 +28,6 @@ impl BuiltinRuntime {
} }
} }
// fn from_builtin_runtime_options<G>(
// eval_mode: EvalMode,
// cost: CostingFun<G>,
// runtime_options: BuiltinRuntimeOptions<T, G>,
// ) -> BuiltinRuntime {
// Self {
// budget: (runtime_options.budget)(cost),
// }
// }
pub fn is_arrow(&self) -> bool { pub fn is_arrow(&self) -> bool {
self.args.len() != self.fun.arity() self.args.len() != self.fun.arity()
} }
@ -209,7 +192,7 @@ impl DefaultFunction {
if arg.is_integer() { if arg.is_integer() {
Ok(()) Ok(())
} else { } else {
todo!("type error") Err(Error::TypeMismatch(Type::Integer, arg.into()))
} }
} }
DefaultFunction::SubtractInteger => todo!(), DefaultFunction::SubtractInteger => todo!(),
@ -240,16 +223,12 @@ impl DefaultFunction {
DefaultFunction::EncodeUtf8 => todo!(), DefaultFunction::EncodeUtf8 => todo!(),
DefaultFunction::DecodeUtf8 => todo!(), DefaultFunction::DecodeUtf8 => todo!(),
DefaultFunction::IfThenElse => { DefaultFunction::IfThenElse => {
if args.is_empty() { if args.is_empty() && !arg.is_bool() {
if arg.is_bool() { Err(Error::TypeMismatch(Type::Bool, arg.into()))
return Ok(());
} else { } else {
todo!("type error")
}
}
Ok(()) Ok(())
} }
}
DefaultFunction::ChooseUnit => todo!(), DefaultFunction::ChooseUnit => todo!(),
DefaultFunction::Trace => todo!(), DefaultFunction::Trace => todo!(),
DefaultFunction::FstPair => todo!(), DefaultFunction::FstPair => todo!(),
@ -278,18 +257,19 @@ impl DefaultFunction {
} }
} }
// This should be safe because we've already checked
// the types of the args as they were pushed. Although
// the unreachables look ugly, it's the reality of the situation.
pub fn call(&self, args: &[Value]) -> Result<Value, Error> { pub fn call(&self, args: &[Value]) -> Result<Value, Error> {
match self { match self {
DefaultFunction::AddInteger => { DefaultFunction::AddInteger => {
assert_eq!(args.len(), self.arity());
let args = (&args[0], &args[1]); let args = (&args[0], &args[1]);
match args { match args {
(Value::Con(Constant::Integer(arg1)), Value::Con(Constant::Integer(arg2))) => { (Value::Con(Constant::Integer(arg1)), Value::Con(Constant::Integer(arg2))) => {
Ok(Value::Con(Constant::Integer(arg1 + arg2))) Ok(Value::Con(Constant::Integer(arg1 + arg2)))
} }
_ => todo!("handle error"), _ => unreachable!(),
} }
} }
DefaultFunction::SubtractInteger => todo!(), DefaultFunction::SubtractInteger => todo!(),
@ -327,7 +307,7 @@ impl DefaultFunction {
Ok(args[2].clone()) Ok(args[2].clone())
} }
} }
_ => todo!("handle error"), _ => unreachable!(),
}, },
DefaultFunction::ChooseUnit => todo!(), DefaultFunction::ChooseUnit => todo!(),
DefaultFunction::Trace => todo!(), DefaultFunction::Trace => todo!(),

View File

@ -82,10 +82,10 @@ peg::parser! {
} }
rule delay() -> Term<Name> rule delay() -> Term<Name>
= "(" _* "delay" _+ t:term() _* ")" { Term::Delay(Box::new(t)) } = "(" _* "delay" _* t:term() _* ")" { Term::Delay(Box::new(t)) }
rule force() -> Term<Name> rule force() -> Term<Name>
= "(" _* "force" _+ t:term() _* ")" { Term::Force(Box::new(t)) } = "(" _* "force" _* t:term() _* ")" { Term::Force(Box::new(t)) }
rule error() -> Term<Name> rule error() -> Term<Name>
= "(" _* "error" _* ")" { Term::Error } = "(" _* "error" _* ")" { Term::Error }

View File

@ -1,8 +1,14 @@
use pretty::RcDoc; use pretty::RcDoc;
use crate::ast::{Constant, Name, Program, Term}; use crate::{
ast::{Constant, Program, Term},
flat::Binder,
};
impl Program<Name> { impl<'a, T> Program<T>
where
T: Binder<'a>,
{
pub fn to_pretty(&self) -> String { pub fn to_pretty(&self) -> String {
let mut w = Vec::new(); let mut w = Vec::new();
@ -40,7 +46,10 @@ impl Program<Name> {
} }
} }
impl Term<Name> { impl<'a, T> Term<T>
where
T: Binder<'a>,
{
pub fn to_pretty(&self) -> String { pub fn to_pretty(&self) -> String {
let mut w = Vec::new(); let mut w = Vec::new();
@ -65,7 +74,7 @@ impl Term<Name> {
fn to_doc(&self) -> RcDoc<()> { fn to_doc(&self) -> RcDoc<()> {
match self { match self {
Term::Var(name) => RcDoc::text(&name.text), Term::Var(name) => RcDoc::text(name.text()),
Term::Delay(term) => RcDoc::text("(") Term::Delay(term) => RcDoc::text("(")
.append( .append(
RcDoc::text("delay") RcDoc::text("delay")
@ -82,7 +91,7 @@ impl Term<Name> {
.append( .append(
RcDoc::text("lam") RcDoc::text("lam")
.append(RcDoc::line()) .append(RcDoc::line())
.append(RcDoc::text(&parameter_name.text)) .append(RcDoc::text(parameter_name.text()))
.append(RcDoc::line()) .append(RcDoc::line())
.append(body.to_doc()) .append(body.to_doc())
.nest(2), .nest(2),
@ -161,7 +170,7 @@ impl Constant {
.append(RcDoc::text("()")), .append(RcDoc::text("()")),
Constant::Bool(b) => RcDoc::text("bool") Constant::Bool(b) => RcDoc::text("bool")
.append(RcDoc::line()) .append(RcDoc::line())
.append(RcDoc::text(if *b { "true" } else { "false" })), .append(RcDoc::text(if *b { "True" } else { "False" })),
} }
} }
} }