diff --git a/README.md b/README.md index f8fd4cc4..c2834767 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,28 @@ A cardano smart contract language and toolchain +## Install + +For now you'll need rust installed, see [rustup](https://rustup.rs). + +`cargo install aiken` + +## Usage + +For now the command line application can only encode/decode Untyped Plutus Core +to/from it's on chain format. See the roadmap below for a list of planned features and goals. + +```sh +# compile an untyped plutus core program +aiken uplc flat program.uplc +``` + ## Roadmap +In general, the goal is to port everything we need for plutus to +Rust. This will be needed if we ever want to build a full node in +Rust. Since we will have these tools natively in Rust, we plan on +building a new high level language for writing smart contracts on Cardano. These are generic milestones and the listed ordering is not necessariy the implementation order or full scope. diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 72b81c0b..abe25f89 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -2,21 +2,28 @@ use std::path::PathBuf; use clap::{Parser, Subcommand}; +/// Cardano smart contract toolchain #[derive(Parser)] #[clap(author, version, about, long_about = None)] #[clap(propagate_version = true)] pub enum Cli { + /// A subcommand for working with Untyped Plutus Core #[clap(subcommand)] Uplc(UplcCommand), } +/// Commands for working with Untyped Plutus Core #[derive(Subcommand)] pub enum UplcCommand { + /// Encode textual Untyped Plutus Core to flat bytes Flat { input: PathBuf, #[clap(short, long)] print: bool, + #[clap(short, long)] + out: Option, }, + /// Decode flat bytes to textual Untyped Plutus Core Unflat { input: PathBuf, #[clap(short, long)] diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 7ae58c24..1266381f 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,3 +1,5 @@ +use std::fs; + use uplc::{ ast::{DeBruijn, FakeNamedDeBruijn, Program}, parser, @@ -10,7 +12,7 @@ fn main() -> anyhow::Result<()> { match args { Cli::Uplc(uplc) => match uplc { - UplcCommand::Flat { input, print } => { + UplcCommand::Flat { input, print, out } => { let code = std::fs::read_to_string(&input)?; let program = parser::program(&code)?; @@ -31,6 +33,14 @@ fn main() -> anyhow::Result<()> { } println!(); + } else { + let out_name = if let Some(out) = out { + out + } else { + format!("{}.flat", input.file_stem().unwrap().to_str().unwrap()) + }; + + fs::write(&out_name, &bytes)?; } } UplcCommand::Unflat { input, print } => { diff --git a/crates/uplc/src/ast.rs b/crates/uplc/src/ast.rs index 04702b39..6344c6dc 100644 --- a/crates/uplc/src/ast.rs +++ b/crates/uplc/src/ast.rs @@ -5,12 +5,20 @@ use crate::{ debruijn::{self, Converter}, }; +/// This represents a program in Untyped Plutus Core. +/// A program contains a version tuple and a term. +/// It is generic because Term requires a generic type. #[derive(Debug, Clone, PartialEq)] pub struct Program { pub version: (usize, usize, usize), pub term: Term, } +/// This represents a term in Untyped Plutus Core. +/// We need a generic type for the different forms that a program may be in. +/// Specifically, `Var` and `parameter_name` in `Lambda` can be a `Name`, +/// `NamedDebruijn`, or `DeBruijn`. When encoded to flat for on chain usage +/// we must encode using the `DeBruijn` form. #[derive(Debug, Clone, PartialEq)] pub enum Term { // tag: 0 @@ -37,6 +45,8 @@ pub enum Term { Builtin(DefaultFunction), } +/// A container for the various constants that are available +/// in Untyped Plutus Core. Used in the `Constant` variant of `Term`. #[derive(Debug, Clone, PartialEq)] pub enum Constant { // tag: 0 @@ -53,20 +63,27 @@ pub enum Constant { Bool(bool), } +/// A Name containing it's parsed textual representation +/// and a unique id from string interning. The Name's text is +/// interned during parsing. #[derive(Debug, Clone, PartialEq)] pub struct Name { pub text: String, pub unique: Unique, } +/// A unique id used for string interning. #[derive(Debug, Clone, PartialEq, Copy, Eq, Hash)] pub struct Unique(isize); impl Unique { + /// Create a new unique id. pub fn new(unique: isize) -> Self { Unique(unique) } + /// Increment the available unique id. This is used during + /// string interning to get the next available unique id. pub fn increment(&mut self) { self.0 += 1; } @@ -90,12 +107,18 @@ impl Display for Unique { } } +/// Similar to `Name` but for Debruijn indices. +/// `Name` is replaced by `NamedDebruijn` when converting +/// program to it's debruijn form. #[derive(Debug, Clone, PartialEq)] pub struct NamedDeBruijn { pub text: String, pub index: DeBruijn, } +/// This is useful for decoding a on chain program into debruijn form. +/// 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); @@ -123,10 +146,12 @@ impl From for FakeNamedDeBruijn { } } +/// Represents a debruijn index. #[derive(Debug, Clone, PartialEq, Copy)] pub struct DeBruijn(usize); impl DeBruijn { + /// Create a new debruijn index. pub fn new(index: usize) -> Self { DeBruijn(index) } @@ -153,12 +178,15 @@ impl From for DeBruijn { impl From for NamedDeBruijn { fn from(index: DeBruijn) -> Self { NamedDeBruijn { + // Inject fake name. We got `i` from the Plutus code base. text: String::from("i"), index, } } } +/// Convert a Parsed `Program` to a `Program` in `NamedDebruijn` form. +/// This checks for any Free Uniques in the `Program` and returns an error if found. impl TryFrom> for Program { type Error = debruijn::Error; @@ -170,6 +198,8 @@ impl TryFrom> for Program { } } +/// Convert a Parsed `Term` to a `Term` in `NamedDebruijn` form. +/// This checks for any Free Uniques in the `Term` and returns an error if found. impl TryFrom> for Term { type Error = debruijn::Error; @@ -182,6 +212,8 @@ impl TryFrom> for Term { } } +/// Convert a Parsed `Program` to a `Program` in `Debruijn` form. +/// This checks for any Free Uniques in the `Program` and returns an error if found. impl TryFrom> for Program { type Error = debruijn::Error; @@ -193,6 +225,8 @@ impl TryFrom> for Program { } } +/// Convert a Parsed `Term` to a `Term` in `Debruijn` form. +/// This checks for any Free Uniques in the `Program` and returns an error if found. impl TryFrom> for Term { type Error = debruijn::Error; diff --git a/crates/uplc/src/builtins.rs b/crates/uplc/src/builtins.rs index 2d7f9135..f4c8b389 100644 --- a/crates/uplc/src/builtins.rs +++ b/crates/uplc/src/builtins.rs @@ -1,6 +1,7 @@ use flat::de; use strum_macros::EnumString; +/// All the possible builtin functions in Untyped Plutus Core. #[repr(u8)] #[allow(non_camel_case_types)] #[derive(Debug, Clone, EnumString, PartialEq, Copy)] diff --git a/crates/uplc/src/parser.rs b/crates/uplc/src/parser.rs index fc91a2b9..2620ea1c 100644 --- a/crates/uplc/src/parser.rs +++ b/crates/uplc/src/parser.rs @@ -10,11 +10,15 @@ use peg::{error::ParseError, str::LineCol}; mod interner; +/// Parse a `Program` from a str. pub fn program(src: &str) -> Result, ParseError> { + // initialize the string interner to get unique name let mut interner = Interner::new(); + // run the generated parser let mut program = uplc::program(src)?; + // assign proper unique ids in place interner.program(&mut program); Ok(program) diff --git a/crates/uplc/src/test.rs b/crates/uplc/src/test.rs index 1417877b..358f9396 100644 --- a/crates/uplc/src/test.rs +++ b/crates/uplc/src/test.rs @@ -1,4 +1,4 @@ -// e2e encoding/decoding tests +/// e2e encoding/decoding tests use crate::{ ast::{DeBruijn, Program}, parser,