chore: switch to a mono repo
This commit is contained in:
11
crates/cli/Cargo.toml
Normal file
11
crates/cli/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "neptune"
|
||||
version = "0.0.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.57"
|
||||
clap = { version = "3.1.14", features = ["derive"] }
|
||||
uplc = { path = '../uplc' }
|
||||
14
crates/cli/src/lib.rs
Normal file
14
crates/cli/src/lib.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Parser)]
|
||||
pub struct Cli {
|
||||
pub input: PathBuf,
|
||||
}
|
||||
|
||||
impl Default for Cli {
|
||||
fn default() -> Self {
|
||||
Self::parse()
|
||||
}
|
||||
}
|
||||
15
crates/cli/src/main.rs
Normal file
15
crates/cli/src/main.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use uplc::parser;
|
||||
|
||||
use neptune::Cli;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let args = Cli::default();
|
||||
|
||||
let code = std::fs::read_to_string(&args.input)?;
|
||||
|
||||
let program = parser::program(&code)?;
|
||||
|
||||
println!("{:#?}", program);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
8
crates/flat/Cargo.toml
Normal file
8
crates/flat/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "flat"
|
||||
version = "0.0.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
34
crates/flat/src/encode.rs
Normal file
34
crates/flat/src/encode.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use crate::{encoder::Encoder, filler::Filler};
|
||||
|
||||
pub trait Encode {
|
||||
fn encode(&self, e: &mut Encoder) -> Result<(), String>;
|
||||
}
|
||||
|
||||
impl Encode for bool {
|
||||
fn encode(&self, e: &mut Encoder) -> Result<(), String> {
|
||||
e.bool(*self);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, K> Encode for (T, K)
|
||||
where
|
||||
T: Encode,
|
||||
K: Encode,
|
||||
{
|
||||
fn encode(&self, e: &mut Encoder) -> Result<(), String> {
|
||||
self.0.encode(e)?;
|
||||
self.1.encode(e)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for Filler {
|
||||
fn encode(&self, e: &mut Encoder) -> Result<(), String> {
|
||||
e.filler();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
78
crates/flat/src/encoder.rs
Normal file
78
crates/flat/src/encoder.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
use crate::encode::Encode;
|
||||
|
||||
pub struct Encoder {
|
||||
pub buffer: Vec<u8>,
|
||||
// Int
|
||||
used_bits: usize,
|
||||
// Int
|
||||
current_byte: u8,
|
||||
}
|
||||
|
||||
impl Default for Encoder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoder {
|
||||
pub fn new() -> Encoder {
|
||||
Encoder {
|
||||
buffer: Vec::new(),
|
||||
used_bits: 0,
|
||||
current_byte: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode any type that implements [`Encode`].
|
||||
pub fn encode<T: Encode>(&mut self, x: T) -> Result<&mut Self, String> {
|
||||
x.encode(self)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn u8(&mut self, x: u8) -> Result<&mut Self, String> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Encode a `bool` value.
|
||||
pub fn bool(&mut self, x: bool) -> &mut Self {
|
||||
if x {
|
||||
self.one()
|
||||
} else {
|
||||
self.zero()
|
||||
}
|
||||
}
|
||||
|
||||
fn zero(&mut self) -> &mut Self {
|
||||
if self.used_bits == 7 {
|
||||
self.next_word();
|
||||
} else {
|
||||
self.used_bits += 1;
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn one(&mut self) -> &mut Self {
|
||||
if self.used_bits == 7 {
|
||||
self.current_byte |= 1;
|
||||
self.next_word();
|
||||
} else {
|
||||
self.current_byte |= 128 >> self.used_bits;
|
||||
self.used_bits += 1;
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn next_word(&mut self) {
|
||||
self.buffer.push(self.current_byte);
|
||||
|
||||
self.current_byte = 0;
|
||||
self.used_bits = 0;
|
||||
}
|
||||
|
||||
pub(crate) fn filler(&mut self) {
|
||||
self.current_byte |= 1;
|
||||
self.next_word();
|
||||
}
|
||||
}
|
||||
13
crates/flat/src/filler.rs
Normal file
13
crates/flat/src/filler.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
pub enum Filler {
|
||||
FillerStart(Box<Filler>),
|
||||
FillerEnd,
|
||||
}
|
||||
|
||||
impl Filler {
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
Filler::FillerStart(f) => f.len() + 1,
|
||||
Filler::FillerEnd => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
33
crates/flat/src/lib.rs
Normal file
33
crates/flat/src/lib.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
mod encode;
|
||||
mod encoder;
|
||||
mod filler;
|
||||
|
||||
pub mod en {
|
||||
pub use super::encode::*;
|
||||
pub use super::encoder::*;
|
||||
}
|
||||
|
||||
pub fn encode<T>(value: T) -> Result<Vec<u8>, String>
|
||||
where
|
||||
T: en::Encode,
|
||||
{
|
||||
let mut e = en::Encoder::new();
|
||||
|
||||
e.encode((value, filler::Filler::FillerEnd))?;
|
||||
|
||||
Ok(e.buffer)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn encode_bool() {
|
||||
let bytes = super::encode(true).unwrap();
|
||||
|
||||
assert_eq!(bytes, vec![0b10000001]);
|
||||
|
||||
let bytes = super::encode(false).unwrap();
|
||||
|
||||
assert_eq!(bytes, vec![0b00000001]);
|
||||
}
|
||||
}
|
||||
14
crates/uplc/Cargo.toml
Normal file
14
crates/uplc/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "uplc"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.57"
|
||||
combine = "4.6.4"
|
||||
flat = { path = "../flat" }
|
||||
hex = "0.4.3"
|
||||
strum = "0.24.0"
|
||||
strum_macros = "0.24.0"
|
||||
3
crates/uplc/example/plutus-core
Normal file
3
crates/uplc/example/plutus-core
Normal file
@@ -0,0 +1,3 @@
|
||||
(program 1.0.0
|
||||
[[(builtin addInteger) (con integer 4)] (con integer 8)]
|
||||
)
|
||||
50
crates/uplc/src/ast.rs
Normal file
50
crates/uplc/src/ast.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use crate::builtins::DefaultFunction;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Program {
|
||||
pub version: String,
|
||||
pub term: Term,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Term {
|
||||
// tag: 0
|
||||
Var(String),
|
||||
// tag: 1
|
||||
Delay(Box<Term>),
|
||||
// tag: 2
|
||||
Lambda {
|
||||
parameter_name: String,
|
||||
body: Box<Term>,
|
||||
},
|
||||
// tag: 3
|
||||
Apply {
|
||||
function: Box<Term>,
|
||||
argument: Box<Term>,
|
||||
},
|
||||
// tag: 4
|
||||
Constant(Constant),
|
||||
// tag: 5
|
||||
Force(Box<Term>),
|
||||
// tag: 6
|
||||
Error,
|
||||
// tag: 7
|
||||
Builtin(DefaultFunction),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Constant {
|
||||
// TODO: figure out the right size for this
|
||||
// tag: 0
|
||||
Integer(i64),
|
||||
// tag: 1
|
||||
ByteString(Vec<u8>),
|
||||
// tag: 2
|
||||
String(String),
|
||||
// tag: 3
|
||||
Char(char),
|
||||
// tag: 4
|
||||
Unit,
|
||||
// tag: 5
|
||||
Bool(bool),
|
||||
}
|
||||
79
crates/uplc/src/builtins.rs
Normal file
79
crates/uplc/src/builtins.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
use strum_macros::EnumString;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Debug, Clone, EnumString)]
|
||||
#[strum(serialize_all = "camelCase")]
|
||||
pub enum DefaultFunction {
|
||||
// Integer functions
|
||||
AddInteger,
|
||||
SubtractInteger,
|
||||
MultiplyInteger,
|
||||
DivideInteger,
|
||||
QuotientInteger,
|
||||
RemainderInteger,
|
||||
ModInteger,
|
||||
EqualsInteger,
|
||||
LessThanInteger,
|
||||
LessThanEqualsInteger,
|
||||
// ByteString functions
|
||||
AppendByteString,
|
||||
ConsByteString,
|
||||
SliceByteString,
|
||||
LengthOfByteString,
|
||||
IndexByteString,
|
||||
EqualsByteString,
|
||||
LessThanByteString,
|
||||
LessThanEqualsByteString,
|
||||
// Cryptography and hash functions
|
||||
#[strum(serialize = "sha2_256")]
|
||||
Sha2_256,
|
||||
Sha3_256,
|
||||
Blake2b_256,
|
||||
VerifySignature,
|
||||
VerifyEcdsaSecp256k1Signature,
|
||||
VerifySchnorrSecp256k1Signature,
|
||||
// String functions
|
||||
AppendString,
|
||||
EqualsString,
|
||||
EncodeUtf8,
|
||||
DecodeUtf8,
|
||||
// Bool function
|
||||
IfThenElse,
|
||||
// Unit function
|
||||
ChooseUnit,
|
||||
// Tracing function
|
||||
Trace,
|
||||
// Pairs functions
|
||||
FstPair,
|
||||
SndPair,
|
||||
// List functions
|
||||
ChooseList,
|
||||
MkCons,
|
||||
HeadList,
|
||||
TailList,
|
||||
NullList,
|
||||
// Data functions
|
||||
// It is convenient to have a "choosing" function for a data type that has more than two
|
||||
// constructors to get pattern matching over it and we may end up having multiple such data
|
||||
// types, hence we include the name of the data type as a suffix.
|
||||
ChooseData,
|
||||
ConstrData,
|
||||
MapData,
|
||||
ListData,
|
||||
IData,
|
||||
BData,
|
||||
UnConstrData,
|
||||
UnMapData,
|
||||
UnListData,
|
||||
UnIData,
|
||||
UnBData,
|
||||
EqualsData,
|
||||
SerialiseData,
|
||||
// Misc constructors
|
||||
// Constructors that we need for constructing e.g. Data. Polymorphic builtin
|
||||
// constructors are often problematic (See note [Representable built-in
|
||||
// functions over polymorphic built-in types])
|
||||
MkPairData,
|
||||
MkNilData,
|
||||
MkNilPairData,
|
||||
}
|
||||
6
crates/uplc/src/lib.rs
Normal file
6
crates/uplc/src/lib.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
pub mod ast;
|
||||
pub mod builtins;
|
||||
pub mod parser;
|
||||
|
||||
#[macro_use]
|
||||
extern crate combine;
|
||||
270
crates/uplc/src/parser.rs
Normal file
270
crates/uplc/src/parser.rs
Normal file
@@ -0,0 +1,270 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use combine::{
|
||||
attempt, between, choice, many1,
|
||||
parser::char::{alpha_num, digit, hex_digit, space, spaces, string},
|
||||
skip_many1,
|
||||
stream::position,
|
||||
token, EasyParser, ParseError, Parser, Stream,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ast::{Constant, Program, Term},
|
||||
builtins::DefaultFunction,
|
||||
};
|
||||
|
||||
pub fn program(src: &str) -> anyhow::Result<Program> {
|
||||
let mut parser = program_();
|
||||
|
||||
let result = parser.easy_parse(position::Stream::new(src));
|
||||
|
||||
match result {
|
||||
Ok((program, _)) => Ok(program),
|
||||
Err(err) => Err(anyhow::anyhow!("{}", err)),
|
||||
}
|
||||
}
|
||||
|
||||
fn program_<Input>() -> impl Parser<Input, Output = Program>
|
||||
where
|
||||
Input: Stream<Token = char>,
|
||||
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
|
||||
{
|
||||
let prog = string("program").with(skip_many1(space())).with(
|
||||
(version(), skip_many1(space()), term().skip(spaces()))
|
||||
.map(|(version, _, term)| Program { version, term }),
|
||||
);
|
||||
|
||||
between(token('('), token(')'), prog).skip(spaces())
|
||||
}
|
||||
|
||||
fn version<Input>() -> impl Parser<Input, Output = String>
|
||||
where
|
||||
Input: Stream<Token = char>,
|
||||
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
|
||||
{
|
||||
(
|
||||
many1(digit()),
|
||||
token('.'),
|
||||
many1(digit()),
|
||||
token('.'),
|
||||
many1(digit()),
|
||||
)
|
||||
.map(
|
||||
|(major, _, minor, _, patch): (String, char, String, char, String)| {
|
||||
format!("{}.{}.{}", major, minor, patch)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn term<Input>() -> impl Parser<Input, Output = Term>
|
||||
where
|
||||
Input: Stream<Token = char>,
|
||||
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
|
||||
{
|
||||
choice((
|
||||
attempt(delay()),
|
||||
attempt(lambda()),
|
||||
attempt(apply()),
|
||||
attempt(constant()),
|
||||
attempt(force()),
|
||||
attempt(error()),
|
||||
attempt(builtin()),
|
||||
))
|
||||
}
|
||||
|
||||
parser! {
|
||||
fn term_[I]()(I) -> Term
|
||||
where [I: Stream<Token = char>]
|
||||
{
|
||||
term()
|
||||
}
|
||||
}
|
||||
|
||||
fn delay<Input>() -> impl Parser<Input, Output = Term>
|
||||
where
|
||||
Input: Stream<Token = char>,
|
||||
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
|
||||
{
|
||||
between(
|
||||
token('('),
|
||||
token(')'),
|
||||
string("delay")
|
||||
.with(skip_many1(space()))
|
||||
.with(term_())
|
||||
.map(|term| Term::Delay(Box::new(term))),
|
||||
)
|
||||
}
|
||||
|
||||
fn force<Input>() -> impl Parser<Input, Output = Term>
|
||||
where
|
||||
Input: Stream<Token = char>,
|
||||
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
|
||||
{
|
||||
between(
|
||||
token('('),
|
||||
token(')'),
|
||||
string("force")
|
||||
.with(skip_many1(space()))
|
||||
.with(term_())
|
||||
.map(|term| Term::Force(Box::new(term))),
|
||||
)
|
||||
}
|
||||
|
||||
fn lambda<Input>() -> impl Parser<Input, Output = Term>
|
||||
where
|
||||
Input: Stream<Token = char>,
|
||||
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
|
||||
{
|
||||
between(
|
||||
token('('),
|
||||
token(')'),
|
||||
string("lam")
|
||||
.with(skip_many1(space()))
|
||||
.with((many1(alpha_num()), skip_many1(space()), term_()))
|
||||
.map(|(parameter_name, _, term)| Term::Lambda {
|
||||
parameter_name,
|
||||
body: Box::new(term),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn apply<Input>() -> impl Parser<Input, Output = Term>
|
||||
where
|
||||
Input: Stream<Token = char>,
|
||||
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
|
||||
{
|
||||
between(
|
||||
token('['),
|
||||
token(']'),
|
||||
(term_().skip(skip_many1(space())), term_()).map(|(function, argument)| Term::Apply {
|
||||
function: Box::new(function),
|
||||
argument: Box::new(argument),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn builtin<Input>() -> impl Parser<Input, Output = Term>
|
||||
where
|
||||
Input: Stream<Token = char>,
|
||||
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
|
||||
{
|
||||
between(
|
||||
token('('),
|
||||
token(')'),
|
||||
string("builtin")
|
||||
.with(skip_many1(space()))
|
||||
.with(many1(alpha_num()))
|
||||
.map(|builtin_name: String| {
|
||||
Term::Builtin(DefaultFunction::from_str(&builtin_name).unwrap())
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn error<Input>() -> impl Parser<Input, Output = Term>
|
||||
where
|
||||
Input: Stream<Token = char>,
|
||||
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
|
||||
{
|
||||
between(
|
||||
token('('),
|
||||
token(')'),
|
||||
string("error")
|
||||
.with(skip_many1(space()))
|
||||
.map(|_| Term::Error),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn constant<Input>() -> impl Parser<Input, Output = Term>
|
||||
where
|
||||
Input: Stream<Token = char>,
|
||||
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
|
||||
{
|
||||
between(
|
||||
token('('),
|
||||
token(')'),
|
||||
string("con")
|
||||
.with(skip_many1(space()))
|
||||
.with(choice((
|
||||
attempt(constant_integer()),
|
||||
attempt(constant_bytestring()),
|
||||
attempt(constant_string()),
|
||||
attempt(constant_unit()),
|
||||
attempt(constant_bool()),
|
||||
)))
|
||||
.map(Term::Constant),
|
||||
)
|
||||
}
|
||||
|
||||
fn constant_integer<Input>() -> impl Parser<Input, Output = Constant>
|
||||
where
|
||||
Input: Stream<Token = char>,
|
||||
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
|
||||
{
|
||||
string("integer")
|
||||
.with(skip_many1(space()))
|
||||
.with(many1(digit()))
|
||||
.map(|d: String| Constant::Integer(d.parse::<i64>().unwrap()))
|
||||
}
|
||||
|
||||
fn constant_bytestring<Input>() -> impl Parser<Input, Output = Constant>
|
||||
where
|
||||
Input: Stream<Token = char>,
|
||||
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
|
||||
{
|
||||
string("bytestring")
|
||||
.with(skip_many1(space()))
|
||||
.with(token('#'))
|
||||
.with(many1(hex_digit()))
|
||||
.map(|b: String| Constant::ByteString(hex::decode(b).unwrap()))
|
||||
}
|
||||
|
||||
fn constant_string<Input>() -> impl Parser<Input, Output = Constant>
|
||||
where
|
||||
Input: Stream<Token = char>,
|
||||
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
|
||||
{
|
||||
string("string")
|
||||
.with(skip_many1(space()))
|
||||
.with(between(token('"'), token('"'), many1(alpha_num())))
|
||||
.map(Constant::String)
|
||||
}
|
||||
|
||||
fn constant_unit<Input>() -> impl Parser<Input, Output = Constant>
|
||||
where
|
||||
Input: Stream<Token = char>,
|
||||
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
|
||||
{
|
||||
string("unit")
|
||||
.with(skip_many1(space()))
|
||||
.with(string("()"))
|
||||
.map(|_| Constant::Unit)
|
||||
}
|
||||
|
||||
fn constant_bool<Input>() -> impl Parser<Input, Output = Constant>
|
||||
where
|
||||
Input: Stream<Token = char>,
|
||||
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
|
||||
{
|
||||
string("bool")
|
||||
.with(skip_many1(space()))
|
||||
.with(string("True").or(string("False")))
|
||||
.map(|b| Constant::Bool(b == "True"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use combine::Parser;
|
||||
|
||||
const CODE: &str = include_str!("../example/plutus-core");
|
||||
|
||||
#[test]
|
||||
fn parse_program() {
|
||||
let result = super::program_().parse(CODE);
|
||||
|
||||
assert!(result.is_ok());
|
||||
|
||||
let program = result.unwrap().0;
|
||||
|
||||
assert_eq!(program.version, "1.0.0");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user