diff --git a/add_integers.uplc b/add_integers.uplc index 2d2d3aa9..eab073b4 100644 --- a/add_integers.uplc +++ b/add_integers.uplc @@ -1,4 +1,7 @@ (program 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") + ] +) \ No newline at end of file diff --git a/crates/cli/src/args.rs b/crates/cli/src/args.rs index 02a850ec..fcbe306c 100644 --- a/crates/cli/src/args.rs +++ b/crates/cli/src/args.rs @@ -32,8 +32,11 @@ pub enum UplcCommand { out: Option, }, /// Format an Untyped Plutus Core program - Fmt { input: PathBuf }, - + Fmt { + input: PathBuf, + #[clap(short, long)] + print: bool, + }, /// Evaluate an Untyped Plutus Core program Eval { input: PathBuf, diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 2756d2bd..bd2f2880 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,4 +1,4 @@ -use std::fs; +use std::{fmt::Write as _, fs}; use uplc::{ ast::{DeBruijn, FakeNamedDeBruijn, Name, NamedDeBruijn, Program, Term}, @@ -27,7 +27,7 @@ fn main() -> anyhow::Result<()> { let mut output = String::new(); for (i, byte) in bytes.iter().enumerate() { - output.push_str(&format!("{:08b}", byte)); + let _ = write!(output, "{:08b}", byte); if (i + 1) % 4 == 0 { output.push('\n'); @@ -47,14 +47,18 @@ fn main() -> anyhow::Result<()> { fs::write(&out_name, &bytes)?; } } - UplcCommand::Fmt { input } => { + UplcCommand::Fmt { input, print } => { let code = std::fs::read_to_string(&input)?; let program = parser::program(&code)?; let pretty = program.to_pretty(); - fs::write(&input, pretty)?; + if print { + println!("{}", pretty); + } else { + fs::write(&input, pretty)?; + } } UplcCommand::Unflat { input, print, out } => { 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); } }, } diff --git a/crates/uplc/src/ast.rs b/crates/uplc/src/ast.rs index 58c20adb..2443de28 100644 --- a/crates/uplc/src/ast.rs +++ b/crates/uplc/src/ast.rs @@ -162,7 +162,7 @@ impl PartialEq for NamedDeBruijn { /// It allows for injecting fake textual names while also using Debruijn for decoding /// without having to loop through twice. #[derive(Debug, Clone, PartialEq)] -pub struct FakeNamedDeBruijn(NamedDeBruijn); +pub struct FakeNamedDeBruijn(pub NamedDeBruijn); impl From for FakeNamedDeBruijn { fn from(d: DeBruijn) -> Self { diff --git a/crates/uplc/src/flat.rs b/crates/uplc/src/flat.rs index 23eb3abe..3e4663ec 100644 --- a/crates/uplc/src/flat.rs +++ b/crates/uplc/src/flat.rs @@ -18,6 +18,7 @@ const TERM_TAG_WIDTH: u32 = 4; pub trait Binder<'b>: Encode + Decode<'b> { fn binder_encode(&self, e: &mut Encoder) -> Result<(), en::Error>; fn binder_decode(d: &mut Decoder) -> Result; + fn text(&self) -> &str; } impl<'b, T> Flat<'b> for Program where T: Binder<'b> + Debug {} @@ -246,6 +247,10 @@ impl<'b> Binder<'b> for Name { fn binder_decode(d: &mut Decoder) -> Result { Name::decode(d) } + + fn text(&self) -> &str { + &self.text + } } impl Encode for NamedDeBruijn { @@ -279,6 +284,10 @@ impl<'b> Binder<'b> for NamedDeBruijn { index: DeBruijn::new(0), }) } + + fn text(&self) -> &str { + &self.text + } } impl Encode for DeBruijn { @@ -303,6 +312,10 @@ impl<'b> Binder<'b> for DeBruijn { fn binder_decode(_d: &mut Decoder) -> Result { Ok(DeBruijn::new(0)) } + + fn text(&self) -> &str { + "i" + } } impl Encode for FakeNamedDeBruijn { @@ -333,6 +346,10 @@ impl<'b> Binder<'b> for FakeNamedDeBruijn { Ok(index.into()) } + + fn text(&self) -> &str { + &self.0.text + } } impl Encode for DefaultFunction { diff --git a/crates/uplc/src/machine.rs b/crates/uplc/src/machine.rs index 4a262b8b..540dfd39 100644 --- a/crates/uplc/src/machine.rs +++ b/crates/uplc/src/machine.rs @@ -10,7 +10,7 @@ mod runtime; use cost_model::{ExBudget, StepKind}; pub use error::Error; -use self::{cost_model::CostModel, runtime::BuiltinRuntime}; +use self::{cost_model::CostModel, error::Type, runtime::BuiltinRuntime}; pub struct Machine { costs: CostModel, @@ -198,7 +198,7 @@ impl Machine { term, mut runtime, } => { - let force_term = Term::Force(Box::new(dbg!(term))); + let force_term = Term::Force(Box::new(term)); if runtime.needs_force() { runtime.consume_force(); @@ -207,7 +207,7 @@ impl Machine { self.return_compute(res) } else { - todo!() + Err(Error::BuiltinTermArgumentExpected(force_term)) } } rest => Err(Error::NonPolymorphicInstantiation(rest)), @@ -245,7 +245,7 @@ impl Machine { self.return_compute(res) } else { - todo!() + Err(Error::UnexpectedBuiltinTermArgument(t)) } } rest => Err(Error::NonFunctionalApplication(rest)), @@ -367,7 +367,7 @@ impl Value { } else { //TODO // std::mem::size_of( i.abs() - todo!() + 1 } } 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!(), + } + } +} diff --git a/crates/uplc/src/machine/error.rs b/crates/uplc/src/machine/error.rs index 25a30175..b104906d 100644 --- a/crates/uplc/src/machine/error.rs +++ b/crates/uplc/src/machine/error.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use thiserror::Error; use crate::ast::{NamedDeBruijn, Term}; @@ -18,4 +20,31 @@ pub enum Error { NonPolymorphicInstantiation(Value), #[error("Attempted to apply a non-function: {0:#?}")] 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), + #[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), +} + +#[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"), + } + } } diff --git a/crates/uplc/src/machine/runtime.rs b/crates/uplc/src/machine/runtime.rs index e3d0d779..a213e320 100644 --- a/crates/uplc/src/machine/runtime.rs +++ b/crates/uplc/src/machine/runtime.rs @@ -2,9 +2,8 @@ use crate::{ast::Constant, builtins::DefaultFunction}; use super::{ cost_model::{BuiltinCosts, ExBudget}, - // cost_model::{CostingFun, ExBudget}, - Error, - Value, + error::Type, + Error, Value, }; //#[derive(std::cmp::PartialEq)] @@ -13,12 +12,6 @@ use super::{ // Deferred, //} -// pub struct BuiltinRuntimeOptions { -// immediate_eval: T, -// deferred_eval: T, -// budget: fn(CostingFun) -> fn(Vec) -> ExBudget, -// } - #[derive(Clone, Debug)] pub struct BuiltinRuntime { args: Vec, @@ -35,16 +28,6 @@ impl BuiltinRuntime { } } - // fn from_builtin_runtime_options( - // eval_mode: EvalMode, - // cost: CostingFun, - // runtime_options: BuiltinRuntimeOptions, - // ) -> BuiltinRuntime { - // Self { - // budget: (runtime_options.budget)(cost), - // } - // } - pub fn is_arrow(&self) -> bool { self.args.len() != self.fun.arity() } @@ -209,7 +192,7 @@ impl DefaultFunction { if arg.is_integer() { Ok(()) } else { - todo!("type error") + Err(Error::TypeMismatch(Type::Integer, arg.into())) } } DefaultFunction::SubtractInteger => todo!(), @@ -240,15 +223,11 @@ impl DefaultFunction { DefaultFunction::EncodeUtf8 => todo!(), DefaultFunction::DecodeUtf8 => todo!(), DefaultFunction::IfThenElse => { - if args.is_empty() { - if arg.is_bool() { - return Ok(()); - } else { - todo!("type error") - } + if args.is_empty() && !arg.is_bool() { + Err(Error::TypeMismatch(Type::Bool, arg.into())) + } else { + Ok(()) } - - Ok(()) } DefaultFunction::ChooseUnit => todo!(), DefaultFunction::Trace => 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 { match self { DefaultFunction::AddInteger => { - assert_eq!(args.len(), self.arity()); - let args = (&args[0], &args[1]); match args { (Value::Con(Constant::Integer(arg1)), Value::Con(Constant::Integer(arg2))) => { Ok(Value::Con(Constant::Integer(arg1 + arg2))) } - _ => todo!("handle error"), + _ => unreachable!(), } } DefaultFunction::SubtractInteger => todo!(), @@ -327,7 +307,7 @@ impl DefaultFunction { Ok(args[2].clone()) } } - _ => todo!("handle error"), + _ => unreachable!(), }, DefaultFunction::ChooseUnit => todo!(), DefaultFunction::Trace => todo!(), diff --git a/crates/uplc/src/parser.rs b/crates/uplc/src/parser.rs index 2620ea1c..fc940c8f 100644 --- a/crates/uplc/src/parser.rs +++ b/crates/uplc/src/parser.rs @@ -82,10 +82,10 @@ peg::parser! { } rule delay() -> Term - = "(" _* "delay" _+ t:term() _* ")" { Term::Delay(Box::new(t)) } + = "(" _* "delay" _* t:term() _* ")" { Term::Delay(Box::new(t)) } rule force() -> Term - = "(" _* "force" _+ t:term() _* ")" { Term::Force(Box::new(t)) } + = "(" _* "force" _* t:term() _* ")" { Term::Force(Box::new(t)) } rule error() -> Term = "(" _* "error" _* ")" { Term::Error } diff --git a/crates/uplc/src/pretty.rs b/crates/uplc/src/pretty.rs index d2e72003..23bdbe1c 100644 --- a/crates/uplc/src/pretty.rs +++ b/crates/uplc/src/pretty.rs @@ -1,8 +1,14 @@ use pretty::RcDoc; -use crate::ast::{Constant, Name, Program, Term}; +use crate::{ + ast::{Constant, Program, Term}, + flat::Binder, +}; -impl Program { +impl<'a, T> Program +where + T: Binder<'a>, +{ pub fn to_pretty(&self) -> String { let mut w = Vec::new(); @@ -40,7 +46,10 @@ impl Program { } } -impl Term { +impl<'a, T> Term +where + T: Binder<'a>, +{ pub fn to_pretty(&self) -> String { let mut w = Vec::new(); @@ -65,7 +74,7 @@ impl Term { fn to_doc(&self) -> RcDoc<()> { match self { - Term::Var(name) => RcDoc::text(&name.text), + Term::Var(name) => RcDoc::text(name.text()), Term::Delay(term) => RcDoc::text("(") .append( RcDoc::text("delay") @@ -82,7 +91,7 @@ impl Term { .append( RcDoc::text("lam") .append(RcDoc::line()) - .append(RcDoc::text(¶meter_name.text)) + .append(RcDoc::text(parameter_name.text())) .append(RcDoc::line()) .append(body.to_doc()) .nest(2), @@ -161,7 +170,7 @@ impl Constant { .append(RcDoc::text("()")), Constant::Bool(b) => RcDoc::text("bool") .append(RcDoc::line()) - .append(RcDoc::text(if *b { "true" } else { "false" })), + .append(RcDoc::text(if *b { "True" } else { "False" })), } } }