chore: switch to a mono repo
This commit is contained in:
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