Merge pull request #835 from aiken-lang/fuzz2
Property-based testing framework
This commit is contained in:
commit
b68f99cf24
|
@ -5,6 +5,10 @@
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- **aiken-lang**: Data now has a generic argument that can be used to specify the blueprint type. @KtorZ
|
- **aiken-lang**: Data now has a generic argument that can be used to specify the blueprint type. @KtorZ
|
||||||
|
- **aiken-lang**: New types `PRNG` and `Fuzzer` in the prelude. @KtorZ
|
||||||
|
- **aiken-lang**: Test definitions now accept an (optional) argument alongside a new keyword `via` to specify fuzzers. @KtorZ
|
||||||
|
- **aiken-project**: Property-based testing framework with integrated shrinking. @KtorZ
|
||||||
|
- **aiken**: The `check` command now accept an extra arg `--seed` to provide an initial seed for the pseudo-random generator of properties. @KtorZ
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -21,6 +21,7 @@ itertools = "0.10.5"
|
||||||
miette = "5.9.0"
|
miette = "5.9.0"
|
||||||
ordinal = "0.3.2"
|
ordinal = "0.3.2"
|
||||||
owo-colors = { version = "3.5.0", features = ["supports-colors"] }
|
owo-colors = { version = "3.5.0", features = ["supports-colors"] }
|
||||||
|
pallas.workspace = true
|
||||||
strum = "0.24.1"
|
strum = "0.24.1"
|
||||||
thiserror = "1.0.39"
|
thiserror = "1.0.39"
|
||||||
vec1 = "1.10.1"
|
vec1 = "1.10.1"
|
||||||
|
|
|
@ -5,6 +5,7 @@ use crate::{
|
||||||
parser::token::{Base, Token},
|
parser::token::{Base, Token},
|
||||||
tipo::{PatternConstructor, Type, TypeInfo},
|
tipo::{PatternConstructor, Type, TypeInfo},
|
||||||
};
|
};
|
||||||
|
use indexmap::IndexMap;
|
||||||
use miette::Diagnostic;
|
use miette::Diagnostic;
|
||||||
use owo_colors::{OwoColorize, Stream::Stdout};
|
use owo_colors::{OwoColorize, Stream::Stdout};
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -127,6 +128,62 @@ impl TypedModule {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Avoid cloning definitions here. This would likely require having a lifetime on
|
||||||
|
// 'Project', so that we can enforce that those references live from the ast to here.
|
||||||
|
pub fn register_definitions(
|
||||||
|
&self,
|
||||||
|
functions: &mut IndexMap<FunctionAccessKey, TypedFunction>,
|
||||||
|
data_types: &mut IndexMap<DataTypeKey, TypedDataType>,
|
||||||
|
) {
|
||||||
|
for def in self.definitions() {
|
||||||
|
match def {
|
||||||
|
Definition::Fn(func) => {
|
||||||
|
functions.insert(
|
||||||
|
FunctionAccessKey {
|
||||||
|
module_name: self.name.clone(),
|
||||||
|
function_name: func.name.clone(),
|
||||||
|
},
|
||||||
|
func.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Definition::Test(test) => {
|
||||||
|
functions.insert(
|
||||||
|
FunctionAccessKey {
|
||||||
|
module_name: self.name.clone(),
|
||||||
|
function_name: test.name.clone(),
|
||||||
|
},
|
||||||
|
test.clone().into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Definition::DataType(dt) => {
|
||||||
|
data_types.insert(
|
||||||
|
DataTypeKey {
|
||||||
|
module_name: self.name.clone(),
|
||||||
|
defined_type: dt.name.clone(),
|
||||||
|
},
|
||||||
|
dt.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Definition::Validator(v) => {
|
||||||
|
let module_name = self.name.as_str();
|
||||||
|
|
||||||
|
if let Some((k, v)) = v.into_function_definition(module_name, |f, _| Some(f)) {
|
||||||
|
functions.insert(k, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((k, v)) = v.into_function_definition(module_name, |_, f| f) {
|
||||||
|
functions.insert(k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Definition::TypeAlias(_) | Definition::ModuleConstant(_) | Definition::Use(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn str_to_keyword(word: &str) -> Option<Token> {
|
fn str_to_keyword(word: &str) -> Option<Token> {
|
||||||
|
@ -154,16 +211,20 @@ fn str_to_keyword(word: &str) -> Option<Token> {
|
||||||
"and" => Some(Token::And),
|
"and" => Some(Token::And),
|
||||||
"or" => Some(Token::Or),
|
"or" => Some(Token::Or),
|
||||||
"validator" => Some(Token::Validator),
|
"validator" => Some(Token::Validator),
|
||||||
|
"via" => Some(Token::Via),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type TypedFunction = Function<Rc<Type>, TypedExpr>;
|
pub type TypedFunction = Function<Rc<Type>, TypedExpr, TypedArg>;
|
||||||
pub type UntypedFunction = Function<(), UntypedExpr>;
|
pub type UntypedFunction = Function<(), UntypedExpr, UntypedArg>;
|
||||||
|
|
||||||
|
pub type TypedTest = Function<Rc<Type>, TypedExpr, TypedArgVia>;
|
||||||
|
pub type UntypedTest = Function<(), UntypedExpr, UntypedArgVia>;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Function<T, Expr> {
|
pub struct Function<T, Expr, Arg> {
|
||||||
pub arguments: Vec<Arg<T>>,
|
pub arguments: Vec<Arg>,
|
||||||
pub body: Expr,
|
pub body: Expr,
|
||||||
pub doc: Option<String>,
|
pub doc: Option<String>,
|
||||||
pub location: Span,
|
pub location: Span,
|
||||||
|
@ -178,9 +239,47 @@ pub struct Function<T, Expr> {
|
||||||
pub type TypedTypeAlias = TypeAlias<Rc<Type>>;
|
pub type TypedTypeAlias = TypeAlias<Rc<Type>>;
|
||||||
pub type UntypedTypeAlias = TypeAlias<()>;
|
pub type UntypedTypeAlias = TypeAlias<()>;
|
||||||
|
|
||||||
impl TypedFunction {
|
impl From<UntypedTest> for UntypedFunction {
|
||||||
|
fn from(f: UntypedTest) -> Self {
|
||||||
|
Function {
|
||||||
|
doc: f.doc,
|
||||||
|
location: f.location,
|
||||||
|
name: f.name,
|
||||||
|
public: f.public,
|
||||||
|
arguments: f.arguments.into_iter().map(|arg| arg.into()).collect(),
|
||||||
|
return_annotation: f.return_annotation,
|
||||||
|
return_type: f.return_type,
|
||||||
|
body: f.body,
|
||||||
|
can_error: f.can_error,
|
||||||
|
end_position: f.end_position,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TypedTest> for TypedFunction {
|
||||||
|
fn from(f: TypedTest) -> Self {
|
||||||
|
Function {
|
||||||
|
doc: f.doc,
|
||||||
|
location: f.location,
|
||||||
|
name: f.name,
|
||||||
|
public: f.public,
|
||||||
|
arguments: f.arguments.into_iter().map(|arg| arg.into()).collect(),
|
||||||
|
return_annotation: f.return_annotation,
|
||||||
|
return_type: f.return_type,
|
||||||
|
body: f.body,
|
||||||
|
can_error: f.can_error,
|
||||||
|
end_position: f.end_position,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypedTest {
|
||||||
pub fn test_hint(&self) -> Option<(BinOp, Box<TypedExpr>, Box<TypedExpr>)> {
|
pub fn test_hint(&self) -> Option<(BinOp, Box<TypedExpr>, Box<TypedExpr>)> {
|
||||||
do_test_hint(&self.body)
|
if self.arguments.is_empty() {
|
||||||
|
do_test_hint(&self.body)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,9 +334,77 @@ pub struct TypeAlias<T> {
|
||||||
pub tipo: T,
|
pub tipo: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
|
pub struct DataTypeKey {
|
||||||
|
pub module_name: String,
|
||||||
|
pub defined_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||||
|
pub struct FunctionAccessKey {
|
||||||
|
pub module_name: String,
|
||||||
|
pub function_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
pub type TypedDataType = DataType<Rc<Type>>;
|
pub type TypedDataType = DataType<Rc<Type>>;
|
||||||
|
|
||||||
impl TypedDataType {
|
impl TypedDataType {
|
||||||
|
pub fn bool() -> Self {
|
||||||
|
DataType {
|
||||||
|
constructors: vec![
|
||||||
|
RecordConstructor {
|
||||||
|
location: Span::empty(),
|
||||||
|
name: "False".to_string(),
|
||||||
|
arguments: vec![],
|
||||||
|
doc: None,
|
||||||
|
sugar: false,
|
||||||
|
},
|
||||||
|
RecordConstructor {
|
||||||
|
location: Span::empty(),
|
||||||
|
name: "True".to_string(),
|
||||||
|
arguments: vec![],
|
||||||
|
doc: None,
|
||||||
|
sugar: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
doc: None,
|
||||||
|
location: Span::empty(),
|
||||||
|
name: "Bool".to_string(),
|
||||||
|
opaque: false,
|
||||||
|
parameters: vec![],
|
||||||
|
public: true,
|
||||||
|
typed_parameters: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prng() -> Self {
|
||||||
|
DataType {
|
||||||
|
constructors: vec![
|
||||||
|
RecordConstructor {
|
||||||
|
location: Span::empty(),
|
||||||
|
name: "Seeded".to_string(),
|
||||||
|
arguments: vec![],
|
||||||
|
doc: None,
|
||||||
|
sugar: false,
|
||||||
|
},
|
||||||
|
RecordConstructor {
|
||||||
|
location: Span::empty(),
|
||||||
|
name: "Replayed".to_string(),
|
||||||
|
arguments: vec![],
|
||||||
|
doc: None,
|
||||||
|
sugar: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
doc: None,
|
||||||
|
location: Span::empty(),
|
||||||
|
name: "PRNG".to_string(),
|
||||||
|
opaque: false,
|
||||||
|
parameters: vec![],
|
||||||
|
public: true,
|
||||||
|
typed_parameters: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn ordering() -> Self {
|
pub fn ordering() -> Self {
|
||||||
DataType {
|
DataType {
|
||||||
constructors: vec![
|
constructors: vec![
|
||||||
|
@ -358,18 +525,51 @@ pub type UntypedValidator = Validator<(), UntypedExpr>;
|
||||||
pub struct Validator<T, Expr> {
|
pub struct Validator<T, Expr> {
|
||||||
pub doc: Option<String>,
|
pub doc: Option<String>,
|
||||||
pub end_position: usize,
|
pub end_position: usize,
|
||||||
pub fun: Function<T, Expr>,
|
pub fun: Function<T, Expr, Arg<T>>,
|
||||||
pub other_fun: Option<Function<T, Expr>>,
|
pub other_fun: Option<Function<T, Expr, Arg<T>>>,
|
||||||
pub location: Span,
|
pub location: Span,
|
||||||
pub params: Vec<Arg<T>>,
|
pub params: Vec<Arg<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TypedValidator {
|
||||||
|
pub fn into_function_definition<'a, F>(
|
||||||
|
&'a self,
|
||||||
|
module_name: &str,
|
||||||
|
select: F,
|
||||||
|
) -> Option<(FunctionAccessKey, TypedFunction)>
|
||||||
|
where
|
||||||
|
F: Fn(&'a TypedFunction, Option<&'a TypedFunction>) -> Option<&'a TypedFunction> + 'a,
|
||||||
|
{
|
||||||
|
match select(&self.fun, self.other_fun.as_ref()) {
|
||||||
|
None => None,
|
||||||
|
Some(fun) => {
|
||||||
|
let mut fun = fun.clone();
|
||||||
|
|
||||||
|
fun.arguments = self
|
||||||
|
.params
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.chain(fun.arguments)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Some((
|
||||||
|
FunctionAccessKey {
|
||||||
|
module_name: module_name.to_string(),
|
||||||
|
function_name: fun.name.clone(),
|
||||||
|
},
|
||||||
|
fun,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type TypedDefinition = Definition<Rc<Type>, TypedExpr, String>;
|
pub type TypedDefinition = Definition<Rc<Type>, TypedExpr, String>;
|
||||||
pub type UntypedDefinition = Definition<(), UntypedExpr, ()>;
|
pub type UntypedDefinition = Definition<(), UntypedExpr, ()>;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Definition<T, Expr, PackageName> {
|
pub enum Definition<T, Expr, PackageName> {
|
||||||
Fn(Function<T, Expr>),
|
Fn(Function<T, Expr, Arg<T>>),
|
||||||
|
|
||||||
TypeAlias(TypeAlias<T>),
|
TypeAlias(TypeAlias<T>),
|
||||||
|
|
||||||
|
@ -379,7 +579,7 @@ pub enum Definition<T, Expr, PackageName> {
|
||||||
|
|
||||||
ModuleConstant(ModuleConstant<T>),
|
ModuleConstant(ModuleConstant<T>),
|
||||||
|
|
||||||
Test(Function<T, Expr>),
|
Test(Function<T, Expr, ArgVia<T, Expr>>),
|
||||||
|
|
||||||
Validator(Validator<T, Expr>),
|
Validator(Validator<T, Expr>),
|
||||||
}
|
}
|
||||||
|
@ -634,6 +834,30 @@ impl<A> Arg<A> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type TypedArgVia = ArgVia<Rc<Type>, TypedExpr>;
|
||||||
|
pub type UntypedArgVia = ArgVia<(), UntypedExpr>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct ArgVia<T, Expr> {
|
||||||
|
pub arg_name: ArgName,
|
||||||
|
pub location: Span,
|
||||||
|
pub via: Expr,
|
||||||
|
pub tipo: T,
|
||||||
|
pub annotation: Option<Annotation>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, Ann> From<ArgVia<T, Ann>> for Arg<T> {
|
||||||
|
fn from(arg: ArgVia<T, Ann>) -> Arg<T> {
|
||||||
|
Arg {
|
||||||
|
arg_name: arg.arg_name,
|
||||||
|
location: arg.location,
|
||||||
|
tipo: arg.tipo,
|
||||||
|
annotation: None,
|
||||||
|
doc: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum ArgName {
|
pub enum ArgName {
|
||||||
Discarded {
|
Discarded {
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{Arg, ArgName, CallArg, Function, ModuleKind, Span, TypedDataType, TypedFunction, UnOp},
|
ast::{
|
||||||
|
Arg, ArgName, CallArg, DataTypeKey, Function, FunctionAccessKey, ModuleKind, Span,
|
||||||
|
TypedDataType, TypedFunction, UnOp,
|
||||||
|
},
|
||||||
expr::TypedExpr,
|
expr::TypedExpr,
|
||||||
gen_uplc::builder::{DataTypeKey, FunctionAccessKey},
|
|
||||||
tipo::{
|
tipo::{
|
||||||
fields::FieldMap, Type, TypeConstructor, TypeInfo, TypeVar, ValueConstructor,
|
fields::FieldMap, Type, TypeConstructor, TypeInfo, TypeVar, ValueConstructor,
|
||||||
ValueConstructorVariant,
|
ValueConstructorVariant,
|
||||||
|
@ -26,6 +28,8 @@ pub const STRING: &str = "String";
|
||||||
pub const OPTION: &str = "Option";
|
pub const OPTION: &str = "Option";
|
||||||
pub const ORDERING: &str = "Ordering";
|
pub const ORDERING: &str = "Ordering";
|
||||||
pub const REDEEMER_WRAPPER: &str = "RedeemerWrapper";
|
pub const REDEEMER_WRAPPER: &str = "RedeemerWrapper";
|
||||||
|
pub const PRNG: &str = "PRNG";
|
||||||
|
pub const FUZZER: &str = "Fuzzer";
|
||||||
|
|
||||||
/// Build a prelude that can be injected
|
/// Build a prelude that can be injected
|
||||||
/// into a compiler pipeline
|
/// into a compiler pipeline
|
||||||
|
@ -80,22 +84,7 @@ pub fn prelude(id_gen: &IdGenerator) -> TypeInfo {
|
||||||
// Bool
|
// Bool
|
||||||
prelude.types_constructors.insert(
|
prelude.types_constructors.insert(
|
||||||
BOOL.to_string(),
|
BOOL.to_string(),
|
||||||
vec!["True".to_string(), "False".to_string()],
|
vec!["False".to_string(), "True".to_string()],
|
||||||
);
|
|
||||||
|
|
||||||
prelude.values.insert(
|
|
||||||
"True".to_string(),
|
|
||||||
ValueConstructor::public(
|
|
||||||
bool(),
|
|
||||||
ValueConstructorVariant::Record {
|
|
||||||
module: "".into(),
|
|
||||||
name: "True".to_string(),
|
|
||||||
field_map: None::<FieldMap>,
|
|
||||||
arity: 0,
|
|
||||||
location: Span::empty(),
|
|
||||||
constructors_count: 2,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
prelude.values.insert(
|
prelude.values.insert(
|
||||||
|
@ -113,6 +102,21 @@ pub fn prelude(id_gen: &IdGenerator) -> TypeInfo {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
prelude.values.insert(
|
||||||
|
"True".to_string(),
|
||||||
|
ValueConstructor::public(
|
||||||
|
bool(),
|
||||||
|
ValueConstructorVariant::Record {
|
||||||
|
module: "".into(),
|
||||||
|
name: "True".to_string(),
|
||||||
|
field_map: None::<FieldMap>,
|
||||||
|
arity: 0,
|
||||||
|
location: Span::empty(),
|
||||||
|
constructors_count: 2,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
prelude.types.insert(
|
prelude.types.insert(
|
||||||
BOOL.to_string(),
|
BOOL.to_string(),
|
||||||
TypeConstructor {
|
TypeConstructor {
|
||||||
|
@ -412,6 +416,89 @@ pub fn prelude(id_gen: &IdGenerator) -> TypeInfo {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// PRNG
|
||||||
|
//
|
||||||
|
// pub type PRNG {
|
||||||
|
// Seeded { seed: Int, choices: List<Int> }
|
||||||
|
// Replayed { choices: List<Int> }
|
||||||
|
// }
|
||||||
|
|
||||||
|
prelude.types.insert(
|
||||||
|
PRNG.to_string(),
|
||||||
|
TypeConstructor {
|
||||||
|
location: Span::empty(),
|
||||||
|
parameters: vec![],
|
||||||
|
tipo: prng(),
|
||||||
|
module: "".to_string(),
|
||||||
|
public: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
prelude.types_constructors.insert(
|
||||||
|
PRNG.to_string(),
|
||||||
|
vec!["Seeded".to_string(), "Replayed".to_string()],
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut seeded_fields = HashMap::new();
|
||||||
|
seeded_fields.insert("seed".to_string(), (0, Span::empty()));
|
||||||
|
seeded_fields.insert("choices".to_string(), (1, Span::empty()));
|
||||||
|
prelude.values.insert(
|
||||||
|
"Seeded".to_string(),
|
||||||
|
ValueConstructor::public(
|
||||||
|
function(vec![int(), list(int())], prng()),
|
||||||
|
ValueConstructorVariant::Record {
|
||||||
|
module: "".into(),
|
||||||
|
name: "Seeded".to_string(),
|
||||||
|
field_map: Some(FieldMap {
|
||||||
|
arity: 2,
|
||||||
|
fields: seeded_fields,
|
||||||
|
is_function: false,
|
||||||
|
}),
|
||||||
|
arity: 2,
|
||||||
|
location: Span::empty(),
|
||||||
|
constructors_count: 2,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut replayed_fields = HashMap::new();
|
||||||
|
replayed_fields.insert("choices".to_string(), (0, Span::empty()));
|
||||||
|
prelude.values.insert(
|
||||||
|
"Replayed".to_string(),
|
||||||
|
ValueConstructor::public(
|
||||||
|
function(vec![list(int())], prng()),
|
||||||
|
ValueConstructorVariant::Record {
|
||||||
|
module: "".into(),
|
||||||
|
name: "Replayed".to_string(),
|
||||||
|
field_map: Some(FieldMap {
|
||||||
|
arity: 1,
|
||||||
|
fields: replayed_fields,
|
||||||
|
is_function: false,
|
||||||
|
}),
|
||||||
|
arity: 1,
|
||||||
|
location: Span::empty(),
|
||||||
|
constructors_count: 2,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fuzzer
|
||||||
|
//
|
||||||
|
// pub type Fuzzer<a> =
|
||||||
|
// fn(PRNG) -> Option<(PRNG, a)>
|
||||||
|
|
||||||
|
let fuzzer_value = generic_var(id_gen.next());
|
||||||
|
prelude.types.insert(
|
||||||
|
FUZZER.to_string(),
|
||||||
|
TypeConstructor {
|
||||||
|
location: Span::empty(),
|
||||||
|
parameters: vec![fuzzer_value.clone()],
|
||||||
|
tipo: fuzzer(fuzzer_value),
|
||||||
|
module: "".to_string(),
|
||||||
|
public: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
prelude
|
prelude
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1133,6 +1220,16 @@ pub fn prelude_data_types(id_gen: &IdGenerator) -> IndexMap<DataTypeKey, TypedDa
|
||||||
ordering_data_type,
|
ordering_data_type,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Bool
|
||||||
|
let bool_data_type = TypedDataType::bool();
|
||||||
|
data_types.insert(
|
||||||
|
DataTypeKey {
|
||||||
|
module_name: "".to_string(),
|
||||||
|
defined_type: "Bool".to_string(),
|
||||||
|
},
|
||||||
|
bool_data_type,
|
||||||
|
);
|
||||||
|
|
||||||
// Option
|
// Option
|
||||||
let option_data_type = TypedDataType::option(generic_var(id_gen.next()));
|
let option_data_type = TypedDataType::option(generic_var(id_gen.next()));
|
||||||
data_types.insert(
|
data_types.insert(
|
||||||
|
@ -1143,6 +1240,16 @@ pub fn prelude_data_types(id_gen: &IdGenerator) -> IndexMap<DataTypeKey, TypedDa
|
||||||
option_data_type,
|
option_data_type,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// PRNG
|
||||||
|
let prng_data_type = TypedDataType::prng();
|
||||||
|
data_types.insert(
|
||||||
|
DataTypeKey {
|
||||||
|
module_name: "".to_string(),
|
||||||
|
defined_type: "PRNG".to_string(),
|
||||||
|
},
|
||||||
|
prng_data_type,
|
||||||
|
);
|
||||||
|
|
||||||
data_types
|
data_types
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1213,6 +1320,22 @@ pub fn bool() -> Rc<Type> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn prng() -> Rc<Type> {
|
||||||
|
Rc::new(Type::App {
|
||||||
|
args: vec![],
|
||||||
|
public: true,
|
||||||
|
name: PRNG.to_string(),
|
||||||
|
module: "".to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fuzzer(a: Rc<Type>) -> Rc<Type> {
|
||||||
|
Rc::new(Type::Fn {
|
||||||
|
args: vec![prng()],
|
||||||
|
ret: option(tuple(vec![prng(), a])),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn list(t: Rc<Type>) -> Rc<Type> {
|
pub fn list(t: Rc<Type>) -> Rc<Type> {
|
||||||
Rc::new(Type::App {
|
Rc::new(Type::App {
|
||||||
public: true,
|
public: true,
|
||||||
|
|
|
@ -1,18 +1,23 @@
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use vec1::Vec1;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{
|
ast::{
|
||||||
self, Annotation, Arg, AssignmentKind, BinOp, ByteArrayFormatPreference, CallArg, Curve,
|
self, Annotation, Arg, AssignmentKind, BinOp, ByteArrayFormatPreference, CallArg, Curve,
|
||||||
DefinitionLocation, IfBranch, Located, LogicalOpChainKind, ParsedCallArg, Pattern,
|
DataType, DataTypeKey, DefinitionLocation, IfBranch, Located, LogicalOpChainKind,
|
||||||
RecordUpdateSpread, Span, TraceKind, TypedClause, TypedRecordUpdateArg, UnOp,
|
ParsedCallArg, Pattern, RecordConstructorArg, RecordUpdateSpread, Span, TraceKind,
|
||||||
UntypedClause, UntypedRecordUpdateArg,
|
TypedClause, TypedDataType, TypedRecordUpdateArg, UnOp, UntypedClause,
|
||||||
|
UntypedRecordUpdateArg,
|
||||||
},
|
},
|
||||||
builtins::void,
|
builtins::void,
|
||||||
|
gen_uplc::builder::{
|
||||||
|
check_replaceable_opaque_type, convert_opaque_type, lookup_data_type_by_tipo,
|
||||||
|
},
|
||||||
parser::token::Base,
|
parser::token::Base,
|
||||||
tipo::{ModuleValueConstructor, PatternConstructor, Type, ValueConstructor},
|
tipo::{ModuleValueConstructor, PatternConstructor, Type, TypeVar, ValueConstructor},
|
||||||
};
|
};
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use pallas::ledger::primitives::alonzo::{Constr, PlutusData};
|
||||||
|
use std::rc::Rc;
|
||||||
|
use uplc::{machine::value::from_pallas_bigint, KeyValuePairs};
|
||||||
|
use vec1::Vec1;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum TypedExpr {
|
pub enum TypedExpr {
|
||||||
|
@ -573,6 +578,185 @@ pub const DEFAULT_TODO_STR: &str = "aiken::todo";
|
||||||
pub const DEFAULT_ERROR_STR: &str = "aiken::error";
|
pub const DEFAULT_ERROR_STR: &str = "aiken::error";
|
||||||
|
|
||||||
impl UntypedExpr {
|
impl UntypedExpr {
|
||||||
|
// Reify some opaque 'PlutusData' into an 'UntypedExpr', using a Type annotation. We also need
|
||||||
|
// an extra map to lookup record & enum constructor's names as they're completely erased when
|
||||||
|
// in their PlutusData form, and the Type annotation only contains type name.
|
||||||
|
//
|
||||||
|
// The function performs some sanity check to ensure that the type does indeed somewhat
|
||||||
|
// correspond to the data being given.
|
||||||
|
pub fn reify(
|
||||||
|
data_types: &IndexMap<&DataTypeKey, &TypedDataType>,
|
||||||
|
data: PlutusData,
|
||||||
|
tipo: &Type,
|
||||||
|
) -> Result<Self, String> {
|
||||||
|
if let Type::Var { tipo } = tipo {
|
||||||
|
if let TypeVar::Link { tipo } = &*tipo.borrow() {
|
||||||
|
return UntypedExpr::reify(data_types, data, tipo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Opaque types are tricky. We can't tell from a type only if it is
|
||||||
|
// opaque or not. We have to lookup its datatype definition.
|
||||||
|
//
|
||||||
|
// Also, we can't -- in theory -- peak into an opaque type. More so, if it
|
||||||
|
// has a single constructor with a single argument, it is an zero-cost
|
||||||
|
// wrapper. That means the underlying PlutusData has no residue of that
|
||||||
|
// wrapper. So we have to manually reconstruct it before crawling further
|
||||||
|
// down the type tree.
|
||||||
|
if check_replaceable_opaque_type(tipo, data_types) {
|
||||||
|
let DataType { name, .. } = lookup_data_type_by_tipo(data_types, tipo)
|
||||||
|
.expect("Type just disappeared from known types? {tipo:?}");
|
||||||
|
|
||||||
|
let inner_type = convert_opaque_type(&tipo.clone().into(), data_types, false);
|
||||||
|
|
||||||
|
let value = UntypedExpr::reify(data_types, data, &inner_type)?;
|
||||||
|
|
||||||
|
return Ok(UntypedExpr::Call {
|
||||||
|
location: Span::empty(),
|
||||||
|
arguments: vec![CallArg {
|
||||||
|
label: None,
|
||||||
|
location: Span::empty(),
|
||||||
|
value,
|
||||||
|
}],
|
||||||
|
fun: Box::new(UntypedExpr::Var {
|
||||||
|
name,
|
||||||
|
location: Span::empty(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
match data {
|
||||||
|
PlutusData::BigInt(ref i) => Ok(UntypedExpr::UInt {
|
||||||
|
location: Span::empty(),
|
||||||
|
base: Base::Decimal {
|
||||||
|
numeric_underscore: false,
|
||||||
|
},
|
||||||
|
value: from_pallas_bigint(i).to_string(),
|
||||||
|
}),
|
||||||
|
PlutusData::BoundedBytes(bytes) => Ok(UntypedExpr::ByteArray {
|
||||||
|
location: Span::empty(),
|
||||||
|
bytes: bytes.into(),
|
||||||
|
preferred_format: ByteArrayFormatPreference::HexadecimalString,
|
||||||
|
}),
|
||||||
|
PlutusData::Array(args) => {
|
||||||
|
match tipo {
|
||||||
|
Type::App {
|
||||||
|
module,
|
||||||
|
name,
|
||||||
|
args: type_args,
|
||||||
|
..
|
||||||
|
} if module.is_empty() && name.as_str() == "List" => {
|
||||||
|
if let [inner] = &type_args[..] {
|
||||||
|
Ok(UntypedExpr::List {
|
||||||
|
location: Span::empty(),
|
||||||
|
elements: args
|
||||||
|
.into_iter()
|
||||||
|
.map(|arg| UntypedExpr::reify(data_types, arg, inner))
|
||||||
|
.collect::<Result<Vec<_>, _>>()?,
|
||||||
|
tail: None,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err("invalid List type annotation: the list has multiple type-parameters.".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Type::Tuple { elems } => Ok(UntypedExpr::Tuple {
|
||||||
|
location: Span::empty(),
|
||||||
|
elems: args
|
||||||
|
.into_iter()
|
||||||
|
.zip(elems)
|
||||||
|
.map(|(arg, arg_type)| UntypedExpr::reify(data_types, arg, arg_type))
|
||||||
|
.collect::<Result<Vec<_>, _>>()?,
|
||||||
|
}),
|
||||||
|
_ => Err(format!(
|
||||||
|
"invalid type annotation. expected List but got: {tipo:?}"
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PlutusData::Constr(Constr {
|
||||||
|
tag,
|
||||||
|
any_constructor,
|
||||||
|
fields,
|
||||||
|
}) => {
|
||||||
|
let ix = if tag == 102 {
|
||||||
|
any_constructor.unwrap() as usize
|
||||||
|
} else if tag < 128 {
|
||||||
|
tag as usize - 121
|
||||||
|
} else {
|
||||||
|
tag as usize - 1280 + 7
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Type::App { .. } = tipo {
|
||||||
|
if let Some(DataType { constructors, .. }) =
|
||||||
|
lookup_data_type_by_tipo(data_types, tipo)
|
||||||
|
{
|
||||||
|
let constructor = &constructors[ix];
|
||||||
|
|
||||||
|
return if fields.is_empty() {
|
||||||
|
Ok(UntypedExpr::Var {
|
||||||
|
location: Span::empty(),
|
||||||
|
name: constructor.name.to_string(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let arguments = fields
|
||||||
|
.into_iter()
|
||||||
|
.zip(constructor.arguments.iter())
|
||||||
|
.map(
|
||||||
|
|(
|
||||||
|
field,
|
||||||
|
RecordConstructorArg {
|
||||||
|
ref label,
|
||||||
|
ref tipo,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
)| {
|
||||||
|
UntypedExpr::reify(data_types, field, tipo).map(|value| {
|
||||||
|
CallArg {
|
||||||
|
label: label.clone(),
|
||||||
|
location: Span::empty(),
|
||||||
|
value,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
Ok(UntypedExpr::Call {
|
||||||
|
location: Span::empty(),
|
||||||
|
arguments,
|
||||||
|
fun: Box::new(UntypedExpr::Var {
|
||||||
|
name: constructor.name.to_string(),
|
||||||
|
location: Span::empty(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(format!(
|
||||||
|
"invalid type annotation {tipo:?} for constructor: {tag:?} with {fields:?}"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
PlutusData::Map(indef_or_def) => {
|
||||||
|
let kvs = match indef_or_def {
|
||||||
|
KeyValuePairs::Def(kvs) => kvs,
|
||||||
|
KeyValuePairs::Indef(kvs) => kvs,
|
||||||
|
};
|
||||||
|
|
||||||
|
UntypedExpr::reify(
|
||||||
|
data_types,
|
||||||
|
PlutusData::Array(
|
||||||
|
kvs.into_iter()
|
||||||
|
.map(|(k, v)| PlutusData::Array(vec![k, v]))
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
tipo,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn todo(reason: Option<Self>, location: Span) -> Self {
|
pub fn todo(reason: Option<Self>, location: Span) -> Self {
|
||||||
UntypedExpr::Trace {
|
UntypedExpr::Trace {
|
||||||
location,
|
location,
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{
|
ast::{
|
||||||
Annotation, Arg, ArgName, AssignmentKind, BinOp, ByteArrayFormatPreference, CallArg,
|
Annotation, Arg, ArgName, ArgVia, AssignmentKind, BinOp, ByteArrayFormatPreference,
|
||||||
ClauseGuard, Constant, CurveType, DataType, Definition, Function, IfBranch,
|
CallArg, ClauseGuard, Constant, CurveType, DataType, Definition, Function, IfBranch,
|
||||||
LogicalOpChainKind, ModuleConstant, Pattern, RecordConstructor, RecordConstructorArg,
|
LogicalOpChainKind, ModuleConstant, Pattern, RecordConstructor, RecordConstructorArg,
|
||||||
RecordUpdateSpread, Span, TraceKind, TypeAlias, TypedArg, UnOp, UnqualifiedImport,
|
RecordUpdateSpread, Span, TraceKind, TypeAlias, TypedArg, UnOp, UnqualifiedImport,
|
||||||
UntypedArg, UntypedClause, UntypedClauseGuard, UntypedDefinition, UntypedFunction,
|
UntypedArg, UntypedArgVia, UntypedClause, UntypedClauseGuard, UntypedDefinition,
|
||||||
UntypedModule, UntypedPattern, UntypedRecordUpdateArg, Use, Validator, CAPTURE_VARIABLE,
|
UntypedFunction, UntypedModule, UntypedPattern, UntypedRecordUpdateArg, Use, Validator,
|
||||||
|
CAPTURE_VARIABLE,
|
||||||
},
|
},
|
||||||
docvec,
|
docvec,
|
||||||
expr::{FnStyle, UntypedExpr, DEFAULT_ERROR_STR, DEFAULT_TODO_STR},
|
expr::{FnStyle, UntypedExpr, DEFAULT_ERROR_STR, DEFAULT_TODO_STR},
|
||||||
|
@ -231,16 +232,7 @@ impl<'comments> Formatter<'comments> {
|
||||||
return_annotation,
|
return_annotation,
|
||||||
end_position,
|
end_position,
|
||||||
..
|
..
|
||||||
}) => self.definition_fn(
|
}) => self.definition_fn(public, name, args, return_annotation, body, *end_position),
|
||||||
public,
|
|
||||||
"fn",
|
|
||||||
name,
|
|
||||||
args,
|
|
||||||
return_annotation,
|
|
||||||
body,
|
|
||||||
*end_position,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
|
|
||||||
Definition::Validator(Validator {
|
Definition::Validator(Validator {
|
||||||
end_position,
|
end_position,
|
||||||
|
@ -257,16 +249,7 @@ impl<'comments> Formatter<'comments> {
|
||||||
end_position,
|
end_position,
|
||||||
can_error,
|
can_error,
|
||||||
..
|
..
|
||||||
}) => self.definition_fn(
|
}) => self.definition_test(name, args, body, *end_position, *can_error),
|
||||||
&false,
|
|
||||||
"test",
|
|
||||||
name,
|
|
||||||
args,
|
|
||||||
&None,
|
|
||||||
body,
|
|
||||||
*end_position,
|
|
||||||
*can_error,
|
|
||||||
),
|
|
||||||
|
|
||||||
Definition::TypeAlias(TypeAlias {
|
Definition::TypeAlias(TypeAlias {
|
||||||
alias,
|
alias,
|
||||||
|
@ -488,25 +471,38 @@ impl<'comments> Formatter<'comments> {
|
||||||
commented(doc, comments)
|
commented(doc, comments)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fn_arg_via<'a, A>(&mut self, arg: &'a ArgVia<A, UntypedExpr>) -> Document<'a> {
|
||||||
|
let comments = self.pop_comments(arg.location.start);
|
||||||
|
|
||||||
|
let doc_comments = self.doc_comments(arg.location.start);
|
||||||
|
|
||||||
|
let doc = arg
|
||||||
|
.arg_name
|
||||||
|
.to_doc()
|
||||||
|
.append(" via ")
|
||||||
|
.append(self.expr(&arg.via, false))
|
||||||
|
.group();
|
||||||
|
|
||||||
|
let doc = doc_comments.append(doc.group()).group();
|
||||||
|
|
||||||
|
commented(doc, comments)
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn definition_fn<'a>(
|
fn definition_fn<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
public: &'a bool,
|
public: &'a bool,
|
||||||
keyword: &'a str,
|
|
||||||
name: &'a str,
|
name: &'a str,
|
||||||
args: &'a [UntypedArg],
|
args: &'a [UntypedArg],
|
||||||
return_annotation: &'a Option<Annotation>,
|
return_annotation: &'a Option<Annotation>,
|
||||||
body: &'a UntypedExpr,
|
body: &'a UntypedExpr,
|
||||||
end_location: usize,
|
end_location: usize,
|
||||||
can_error: bool,
|
|
||||||
) -> Document<'a> {
|
) -> Document<'a> {
|
||||||
// Fn name and args
|
// Fn name and args
|
||||||
let head = pub_(*public)
|
let head = pub_(*public)
|
||||||
.append(keyword)
|
.append("fn ")
|
||||||
.append(" ")
|
|
||||||
.append(name)
|
.append(name)
|
||||||
.append(wrap_args(args.iter().map(|e| (self.fn_arg(e), false))))
|
.append(wrap_args(args.iter().map(|e| (self.fn_arg(e), false))));
|
||||||
.append(if can_error { " fail" } else { "" });
|
|
||||||
|
|
||||||
// Add return annotation
|
// Add return annotation
|
||||||
let head = match return_annotation {
|
let head = match return_annotation {
|
||||||
|
@ -531,6 +527,39 @@ impl<'comments> Formatter<'comments> {
|
||||||
.append("}")
|
.append("}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn definition_test<'a>(
|
||||||
|
&mut self,
|
||||||
|
name: &'a str,
|
||||||
|
args: &'a [UntypedArgVia],
|
||||||
|
body: &'a UntypedExpr,
|
||||||
|
end_location: usize,
|
||||||
|
can_error: bool,
|
||||||
|
) -> Document<'a> {
|
||||||
|
// Fn name and args
|
||||||
|
let head = "test "
|
||||||
|
.to_doc()
|
||||||
|
.append(name)
|
||||||
|
.append(wrap_args(args.iter().map(|e| (self.fn_arg_via(e), false))))
|
||||||
|
.append(if can_error { " fail" } else { "" })
|
||||||
|
.group();
|
||||||
|
|
||||||
|
// Format body
|
||||||
|
let body = self.expr(body, true);
|
||||||
|
|
||||||
|
// Add any trailing comments
|
||||||
|
let body = match printed_comments(self.pop_comments(end_location), false) {
|
||||||
|
Some(comments) => body.append(line()).append(comments),
|
||||||
|
None => body,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stick it all together
|
||||||
|
head.append(" {")
|
||||||
|
.append(line().append(body).nest(INDENT).group())
|
||||||
|
.append(line())
|
||||||
|
.append("}")
|
||||||
|
}
|
||||||
|
|
||||||
fn definition_validator<'a>(
|
fn definition_validator<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
params: &'a [UntypedArg],
|
params: &'a [UntypedArg],
|
||||||
|
@ -550,13 +579,11 @@ impl<'comments> Formatter<'comments> {
|
||||||
let first_fn = self
|
let first_fn = self
|
||||||
.definition_fn(
|
.definition_fn(
|
||||||
&false,
|
&false,
|
||||||
"fn",
|
|
||||||
&fun.name,
|
&fun.name,
|
||||||
&fun.arguments,
|
&fun.arguments,
|
||||||
&fun.return_annotation,
|
&fun.return_annotation,
|
||||||
&fun.body,
|
&fun.body,
|
||||||
fun.end_position,
|
fun.end_position,
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
.group();
|
.group();
|
||||||
let first_fn = commented(fun_doc_comments.append(first_fn).group(), fun_comments);
|
let first_fn = commented(fun_doc_comments.append(first_fn).group(), fun_comments);
|
||||||
|
@ -570,13 +597,11 @@ impl<'comments> Formatter<'comments> {
|
||||||
let other_fn = self
|
let other_fn = self
|
||||||
.definition_fn(
|
.definition_fn(
|
||||||
&false,
|
&false,
|
||||||
"fn",
|
|
||||||
&other.name,
|
&other.name,
|
||||||
&other.arguments,
|
&other.arguments,
|
||||||
&other.return_annotation,
|
&other.return_annotation,
|
||||||
&other.body,
|
&other.body,
|
||||||
other.end_position,
|
other.end_position,
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
.group();
|
.group();
|
||||||
|
|
||||||
|
|
|
@ -2,25 +2,21 @@ pub mod air;
|
||||||
pub mod builder;
|
pub mod builder;
|
||||||
pub mod tree;
|
pub mod tree;
|
||||||
|
|
||||||
use petgraph::{algo, Graph};
|
use self::{
|
||||||
use std::collections::HashMap;
|
air::Air,
|
||||||
use std::rc::Rc;
|
builder::{
|
||||||
|
air_holds_msg, cast_validator_args, constants_ir, convert_type_to_data, extract_constant,
|
||||||
use indexmap::{IndexMap, IndexSet};
|
lookup_data_type_by_tipo, modify_cyclic_calls, modify_self_calls, rearrange_list_clauses,
|
||||||
use itertools::Itertools;
|
AssignmentProperties, ClauseProperties, CodeGenSpecialFuncs, CycleFunctionNames,
|
||||||
use uplc::{
|
HoistableFunction, Variant,
|
||||||
ast::{Constant as UplcConstant, Name, NamedDeBruijn, Program, Term, Type as UplcType},
|
},
|
||||||
builder::{CONSTR_FIELDS_EXPOSER, CONSTR_INDEX_EXPOSER, EXPECT_ON_LIST},
|
tree::{AirMsg, AirTree, TreePath},
|
||||||
builtins::DefaultFunction,
|
|
||||||
machine::cost_model::ExBudget,
|
|
||||||
optimize::aiken_optimize_and_intern,
|
|
||||||
parser::interner::Interner,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{
|
ast::{
|
||||||
AssignmentKind, BinOp, Bls12_381Point, Curve, Pattern, Span, TraceLevel, TypedArg,
|
AssignmentKind, BinOp, Bls12_381Point, Curve, DataTypeKey, FunctionAccessKey, Pattern,
|
||||||
TypedClause, TypedDataType, TypedFunction, TypedPattern, TypedValidator, UnOp,
|
Span, TraceLevel, Tracing, TypedArg, TypedClause, TypedDataType, TypedFunction,
|
||||||
|
TypedPattern, TypedValidator, UnOp,
|
||||||
},
|
},
|
||||||
builtins::{bool, data, int, list, string, void},
|
builtins::{bool, data, int, list, string, void},
|
||||||
expr::TypedExpr,
|
expr::TypedExpr,
|
||||||
|
@ -41,25 +37,26 @@ use crate::{
|
||||||
},
|
},
|
||||||
IdGenerator,
|
IdGenerator,
|
||||||
};
|
};
|
||||||
|
use indexmap::{IndexMap, IndexSet};
|
||||||
use self::{
|
use itertools::Itertools;
|
||||||
air::Air,
|
use petgraph::{algo, Graph};
|
||||||
builder::{
|
use std::{collections::HashMap, rc::Rc};
|
||||||
air_holds_msg, cast_validator_args, constants_ir, convert_type_to_data, extract_constant,
|
use uplc::{
|
||||||
lookup_data_type_by_tipo, modify_cyclic_calls, modify_self_calls, rearrange_list_clauses,
|
ast::{Constant as UplcConstant, Name, NamedDeBruijn, Program, Term, Type as UplcType},
|
||||||
AssignmentProperties, ClauseProperties, CodeGenSpecialFuncs, CycleFunctionNames,
|
builder::{CONSTR_FIELDS_EXPOSER, CONSTR_INDEX_EXPOSER, EXPECT_ON_LIST},
|
||||||
DataTypeKey, FunctionAccessKey, HoistableFunction, Variant,
|
builtins::DefaultFunction,
|
||||||
},
|
machine::cost_model::ExBudget,
|
||||||
tree::{AirMsg, AirTree, TreePath},
|
optimize::aiken_optimize_and_intern,
|
||||||
|
parser::interner::Interner,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CodeGenerator<'a> {
|
pub struct CodeGenerator<'a> {
|
||||||
/// immutable index maps
|
/// immutable index maps
|
||||||
functions: IndexMap<FunctionAccessKey, &'a TypedFunction>,
|
functions: IndexMap<&'a FunctionAccessKey, &'a TypedFunction>,
|
||||||
data_types: IndexMap<DataTypeKey, &'a TypedDataType>,
|
data_types: IndexMap<&'a DataTypeKey, &'a TypedDataType>,
|
||||||
module_types: IndexMap<&'a String, &'a TypeInfo>,
|
module_types: IndexMap<&'a str, &'a TypeInfo>,
|
||||||
module_src: IndexMap<String, (String, LineNumbers)>,
|
module_src: IndexMap<&'a str, &'a (String, LineNumbers)>,
|
||||||
/// immutable option
|
/// immutable option
|
||||||
tracing: TraceLevel,
|
tracing: TraceLevel,
|
||||||
/// mutable index maps that are reset
|
/// mutable index maps that are reset
|
||||||
|
@ -74,19 +71,23 @@ pub struct CodeGenerator<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CodeGenerator<'a> {
|
impl<'a> CodeGenerator<'a> {
|
||||||
|
pub fn data_types(&self) -> &IndexMap<&'a DataTypeKey, &'a TypedDataType> {
|
||||||
|
&self.data_types
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
functions: IndexMap<FunctionAccessKey, &'a TypedFunction>,
|
functions: IndexMap<&'a FunctionAccessKey, &'a TypedFunction>,
|
||||||
data_types: IndexMap<DataTypeKey, &'a TypedDataType>,
|
data_types: IndexMap<&'a DataTypeKey, &'a TypedDataType>,
|
||||||
module_types: IndexMap<&'a String, &'a TypeInfo>,
|
module_types: IndexMap<&'a str, &'a TypeInfo>,
|
||||||
module_src: IndexMap<String, (String, LineNumbers)>,
|
module_src: IndexMap<&'a str, &'a (String, LineNumbers)>,
|
||||||
tracing: TraceLevel,
|
tracing: Tracing,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
CodeGenerator {
|
CodeGenerator {
|
||||||
functions,
|
functions,
|
||||||
data_types,
|
data_types,
|
||||||
module_types,
|
module_types,
|
||||||
module_src,
|
module_src,
|
||||||
tracing,
|
tracing: tracing.trace_level(true),
|
||||||
defined_functions: IndexMap::new(),
|
defined_functions: IndexMap::new(),
|
||||||
special_functions: CodeGenSpecialFuncs::new(),
|
special_functions: CodeGenSpecialFuncs::new(),
|
||||||
code_gen_functions: IndexMap::new(),
|
code_gen_functions: IndexMap::new(),
|
||||||
|
@ -107,21 +108,6 @@ impl<'a> CodeGenerator<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_function(
|
|
||||||
&mut self,
|
|
||||||
module_name: String,
|
|
||||||
function_name: String,
|
|
||||||
value: &'a TypedFunction,
|
|
||||||
) -> Option<&'a TypedFunction> {
|
|
||||||
self.functions.insert(
|
|
||||||
FunctionAccessKey {
|
|
||||||
module_name,
|
|
||||||
function_name,
|
|
||||||
},
|
|
||||||
value,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generate(
|
pub fn generate(
|
||||||
&mut self,
|
&mut self,
|
||||||
TypedValidator {
|
TypedValidator {
|
||||||
|
@ -130,16 +116,16 @@ impl<'a> CodeGenerator<'a> {
|
||||||
params,
|
params,
|
||||||
..
|
..
|
||||||
}: &TypedValidator,
|
}: &TypedValidator,
|
||||||
module_name: &String,
|
module_name: &str,
|
||||||
) -> Program<Name> {
|
) -> Program<Name> {
|
||||||
let mut air_tree_fun = self.build(&fun.body, module_name, &[]);
|
let mut air_tree_fun = self.build(&fun.body, module_name, &[]);
|
||||||
|
|
||||||
air_tree_fun = wrap_validator_condition(air_tree_fun, self.tracing);
|
air_tree_fun = wrap_validator_condition(air_tree_fun, self.tracing);
|
||||||
|
|
||||||
let (src_code, lines) = self.module_src.get(module_name).unwrap().clone();
|
let (src_code, lines) = self.module_src.get(module_name).unwrap();
|
||||||
|
|
||||||
let mut validator_args_tree =
|
let mut validator_args_tree =
|
||||||
self.check_validator_args(&fun.arguments, true, air_tree_fun, &src_code, &lines);
|
self.check_validator_args(&fun.arguments, true, air_tree_fun, src_code, lines);
|
||||||
|
|
||||||
validator_args_tree = AirTree::no_op(validator_args_tree);
|
validator_args_tree = AirTree::no_op(validator_args_tree);
|
||||||
|
|
||||||
|
@ -162,8 +148,8 @@ impl<'a> CodeGenerator<'a> {
|
||||||
&other.arguments,
|
&other.arguments,
|
||||||
true,
|
true,
|
||||||
air_tree_fun_other,
|
air_tree_fun_other,
|
||||||
&src_code,
|
src_code,
|
||||||
&lines,
|
lines,
|
||||||
);
|
);
|
||||||
|
|
||||||
validator_args_tree_other = AirTree::no_op(validator_args_tree_other);
|
validator_args_tree_other = AirTree::no_op(validator_args_tree_other);
|
||||||
|
@ -198,8 +184,13 @@ impl<'a> CodeGenerator<'a> {
|
||||||
self.finalize(term)
|
self.finalize(term)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_test(&mut self, test_body: &TypedExpr, module_name: &String) -> Program<Name> {
|
pub fn generate_raw(
|
||||||
let mut air_tree = self.build(test_body, module_name, &[]);
|
&mut self,
|
||||||
|
body: &TypedExpr,
|
||||||
|
args: &[TypedArg],
|
||||||
|
module_name: &str,
|
||||||
|
) -> Program<Name> {
|
||||||
|
let mut air_tree = self.build(body, module_name, &[]);
|
||||||
|
|
||||||
air_tree = AirTree::no_op(air_tree);
|
air_tree = AirTree::no_op(air_tree);
|
||||||
|
|
||||||
|
@ -208,7 +199,13 @@ impl<'a> CodeGenerator<'a> {
|
||||||
// optimizations on air tree
|
// optimizations on air tree
|
||||||
let full_vec = full_tree.to_vec();
|
let full_vec = full_tree.to_vec();
|
||||||
|
|
||||||
let term = self.uplc_code_gen(full_vec);
|
let mut term = self.uplc_code_gen(full_vec);
|
||||||
|
|
||||||
|
term = if args.is_empty() {
|
||||||
|
term
|
||||||
|
} else {
|
||||||
|
cast_validator_args(term, args)
|
||||||
|
};
|
||||||
|
|
||||||
self.finalize(term)
|
self.finalize(term)
|
||||||
}
|
}
|
||||||
|
@ -239,7 +236,7 @@ impl<'a> CodeGenerator<'a> {
|
||||||
fn build(
|
fn build(
|
||||||
&mut self,
|
&mut self,
|
||||||
body: &TypedExpr,
|
body: &TypedExpr,
|
||||||
module_build_name: &String,
|
module_build_name: &str,
|
||||||
context: &[TypedExpr],
|
context: &[TypedExpr],
|
||||||
) -> AirTree {
|
) -> AirTree {
|
||||||
if !context.is_empty() {
|
if !context.is_empty() {
|
||||||
|
@ -254,7 +251,7 @@ impl<'a> CodeGenerator<'a> {
|
||||||
panic!("Dangling expressions without an assignment")
|
panic!("Dangling expressions without an assignment")
|
||||||
};
|
};
|
||||||
|
|
||||||
let replaced_type = convert_opaque_type(tipo, &self.data_types);
|
let replaced_type = convert_opaque_type(tipo, &self.data_types, true);
|
||||||
|
|
||||||
let air_value = self.build(value, module_build_name, &[]);
|
let air_value = self.build(value, module_build_name, &[]);
|
||||||
|
|
||||||
|
@ -449,7 +446,7 @@ impl<'a> CodeGenerator<'a> {
|
||||||
constructor: ModuleValueConstructor::Fn { name, .. },
|
constructor: ModuleValueConstructor::Fn { name, .. },
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let type_info = self.module_types.get(module_name).unwrap();
|
let type_info = self.module_types.get(module_name.as_str()).unwrap();
|
||||||
let value = type_info.values.get(name).unwrap();
|
let value = type_info.values.get(name).unwrap();
|
||||||
|
|
||||||
let ValueConstructorVariant::ModuleFn { builtin, .. } = &value.variant
|
let ValueConstructorVariant::ModuleFn { builtin, .. } = &value.variant
|
||||||
|
@ -722,7 +719,7 @@ impl<'a> CodeGenerator<'a> {
|
||||||
function_name: name.clone(),
|
function_name: name.clone(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let type_info = self.module_types.get(module_name).unwrap();
|
let type_info = self.module_types.get(module_name.as_str()).unwrap();
|
||||||
|
|
||||||
let value = type_info.values.get(name).unwrap();
|
let value = type_info.values.get(name).unwrap();
|
||||||
|
|
||||||
|
@ -894,7 +891,7 @@ impl<'a> CodeGenerator<'a> {
|
||||||
if props.full_check {
|
if props.full_check {
|
||||||
let mut index_map = IndexMap::new();
|
let mut index_map = IndexMap::new();
|
||||||
|
|
||||||
let non_opaque_tipo = convert_opaque_type(tipo, &self.data_types);
|
let non_opaque_tipo = convert_opaque_type(tipo, &self.data_types, true);
|
||||||
|
|
||||||
let val = AirTree::local_var(name, tipo.clone());
|
let val = AirTree::local_var(name, tipo.clone());
|
||||||
|
|
||||||
|
@ -932,7 +929,7 @@ impl<'a> CodeGenerator<'a> {
|
||||||
let name = &format!("__discard_expect_{}", name);
|
let name = &format!("__discard_expect_{}", name);
|
||||||
let mut index_map = IndexMap::new();
|
let mut index_map = IndexMap::new();
|
||||||
|
|
||||||
let non_opaque_tipo = convert_opaque_type(tipo, &self.data_types);
|
let non_opaque_tipo = convert_opaque_type(tipo, &self.data_types, true);
|
||||||
|
|
||||||
let val = AirTree::local_var(name, tipo.clone());
|
let val = AirTree::local_var(name, tipo.clone());
|
||||||
|
|
||||||
|
@ -1325,7 +1322,7 @@ impl<'a> CodeGenerator<'a> {
|
||||||
msg_func: Option<AirMsg>,
|
msg_func: Option<AirMsg>,
|
||||||
) -> AirTree {
|
) -> AirTree {
|
||||||
assert!(tipo.get_generic().is_none());
|
assert!(tipo.get_generic().is_none());
|
||||||
let tipo = &convert_opaque_type(tipo, &self.data_types);
|
let tipo = &convert_opaque_type(tipo, &self.data_types, true);
|
||||||
|
|
||||||
if tipo.is_primitive() {
|
if tipo.is_primitive() {
|
||||||
// Since we would return void anyway and ignore then we can just return value here and ignore
|
// Since we would return void anyway and ignore then we can just return value here and ignore
|
||||||
|
@ -1761,7 +1758,7 @@ impl<'a> CodeGenerator<'a> {
|
||||||
final_clause: TypedClause,
|
final_clause: TypedClause,
|
||||||
subject_tipo: &Rc<Type>,
|
subject_tipo: &Rc<Type>,
|
||||||
props: &mut ClauseProperties,
|
props: &mut ClauseProperties,
|
||||||
module_name: &String,
|
module_name: &str,
|
||||||
) -> AirTree {
|
) -> AirTree {
|
||||||
assert!(
|
assert!(
|
||||||
!subject_tipo.is_void(),
|
!subject_tipo.is_void(),
|
||||||
|
@ -2774,7 +2771,7 @@ impl<'a> CodeGenerator<'a> {
|
||||||
|
|
||||||
let param = AirTree::local_var(&arg_name, data());
|
let param = AirTree::local_var(&arg_name, data());
|
||||||
|
|
||||||
let actual_type = convert_opaque_type(&arg.tipo, &self.data_types);
|
let actual_type = convert_opaque_type(&arg.tipo, &self.data_types, true);
|
||||||
|
|
||||||
let msg_func = match self.tracing {
|
let msg_func = match self.tracing {
|
||||||
TraceLevel::Silent => None,
|
TraceLevel::Silent => None,
|
||||||
|
@ -3565,7 +3562,13 @@ impl<'a> CodeGenerator<'a> {
|
||||||
let code_gen_func = self
|
let code_gen_func = self
|
||||||
.code_gen_functions
|
.code_gen_functions
|
||||||
.get(&generic_function_key.function_name)
|
.get(&generic_function_key.function_name)
|
||||||
.unwrap_or_else(|| panic!("Missing Code Gen Function Definition"));
|
.unwrap_or_else(|| {
|
||||||
|
panic!(
|
||||||
|
"Missing function definition for {}. Known definitions: {:?}",
|
||||||
|
generic_function_key.function_name,
|
||||||
|
self.code_gen_functions.keys(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
if !dependency_functions
|
if !dependency_functions
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -3625,12 +3628,13 @@ impl<'a> CodeGenerator<'a> {
|
||||||
let mut function_def_types = function_def
|
let mut function_def_types = function_def
|
||||||
.arguments
|
.arguments
|
||||||
.iter()
|
.iter()
|
||||||
.map(|arg| convert_opaque_type(&arg.tipo, &self.data_types))
|
.map(|arg| convert_opaque_type(&arg.tipo, &self.data_types, true))
|
||||||
.collect_vec();
|
.collect_vec();
|
||||||
|
|
||||||
function_def_types.push(convert_opaque_type(
|
function_def_types.push(convert_opaque_type(
|
||||||
&function_def.return_type,
|
&function_def.return_type,
|
||||||
&self.data_types,
|
&self.data_types,
|
||||||
|
true,
|
||||||
));
|
));
|
||||||
|
|
||||||
let mono_types: IndexMap<u64, Rc<Type>> = if !function_def_types.is_empty() {
|
let mono_types: IndexMap<u64, Rc<Type>> = if !function_def_types.is_empty() {
|
||||||
|
@ -3832,7 +3836,9 @@ impl<'a> CodeGenerator<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
DefaultFunction::MkCons | DefaultFunction::MkPairData => {
|
DefaultFunction::MkCons | DefaultFunction::MkPairData => {
|
||||||
unimplemented!("MkCons and MkPairData should be handled by an anon function or using [] or ( a, b, .., z).\n")
|
unimplemented!(
|
||||||
|
"MkCons and MkPairData should be handled by an anon function or using [] or ( a, b, .., z).\n"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let mut term: Term<Name> = (*builtin).into();
|
let mut term: Term<Name> = (*builtin).into();
|
||||||
|
@ -4225,7 +4231,9 @@ impl<'a> CodeGenerator<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
DefaultFunction::MkCons | DefaultFunction::MkPairData => {
|
DefaultFunction::MkCons | DefaultFunction::MkPairData => {
|
||||||
unimplemented!("MkCons and MkPairData should be handled by an anon function or using [] or ( a, b, .., z).\n")
|
unimplemented!(
|
||||||
|
"MkCons and MkPairData should be handled by an anon function or using [] or ( a, b, .., z).\n"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let mut term: Term<Name> = func.into();
|
let mut term: Term<Name> = func.into();
|
||||||
|
|
|
@ -1,7 +1,21 @@
|
||||||
use std::{collections::HashMap, ops::Deref, rc::Rc};
|
use super::{
|
||||||
|
air::{Air, ExpectLevel},
|
||||||
|
tree::{AirMsg, AirTree, TreePath},
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
ast::{
|
||||||
|
AssignmentKind, BinOp, ClauseGuard, Constant, DataType, DataTypeKey, FunctionAccessKey,
|
||||||
|
Pattern, Span, TraceLevel, TypedArg, TypedClause, TypedClauseGuard, TypedDataType,
|
||||||
|
TypedPattern, UnOp,
|
||||||
|
},
|
||||||
|
builtins::{bool, data, function, int, list, string, void},
|
||||||
|
expr::TypedExpr,
|
||||||
|
line_numbers::{LineColumn, LineNumbers},
|
||||||
|
tipo::{PatternConstructor, Type, TypeVar, ValueConstructor, ValueConstructorVariant},
|
||||||
|
};
|
||||||
use indexmap::{IndexMap, IndexSet};
|
use indexmap::{IndexMap, IndexSet};
|
||||||
use itertools::{Itertools, Position};
|
use itertools::{Itertools, Position};
|
||||||
|
use std::{collections::HashMap, ops::Deref, rc::Rc};
|
||||||
use uplc::{
|
use uplc::{
|
||||||
ast::{Constant as UplcConstant, Name, Term, Type as UplcType},
|
ast::{Constant as UplcConstant, Name, Term, Type as UplcType},
|
||||||
builder::{CONSTR_FIELDS_EXPOSER, CONSTR_INDEX_EXPOSER},
|
builder::{CONSTR_FIELDS_EXPOSER, CONSTR_INDEX_EXPOSER},
|
||||||
|
@ -13,27 +27,6 @@ use uplc::{
|
||||||
Constr, KeyValuePairs, PlutusData,
|
Constr, KeyValuePairs, PlutusData,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
|
||||||
ast::{
|
|
||||||
AssignmentKind, DataType, Pattern, Span, TraceLevel, TypedArg, TypedClause,
|
|
||||||
TypedClauseGuard, TypedDataType, TypedPattern,
|
|
||||||
},
|
|
||||||
builtins::{bool, data, function, int, list, string, void},
|
|
||||||
expr::TypedExpr,
|
|
||||||
line_numbers::{LineColumn, LineNumbers},
|
|
||||||
tipo::{PatternConstructor, TypeVar, ValueConstructor, ValueConstructorVariant},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
ast::{BinOp, ClauseGuard, Constant, UnOp},
|
|
||||||
tipo::Type,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
air::{Air, ExpectLevel},
|
|
||||||
tree::{AirMsg, AirTree, TreePath},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub type Variant = String;
|
pub type Variant = String;
|
||||||
|
|
||||||
pub type Params = Vec<String>;
|
pub type Params = Vec<String>;
|
||||||
|
@ -68,18 +61,6 @@ pub enum HoistableFunction {
|
||||||
CyclicLink(FunctionAccessKey),
|
CyclicLink(FunctionAccessKey),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
|
||||||
pub struct DataTypeKey {
|
|
||||||
pub module_name: String,
|
|
||||||
pub defined_type: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
|
||||||
pub struct FunctionAccessKey {
|
|
||||||
pub module_name: String,
|
|
||||||
pub function_name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct AssignmentProperties {
|
pub struct AssignmentProperties {
|
||||||
pub value_type: Rc<Type>,
|
pub value_type: Rc<Type>,
|
||||||
|
@ -312,7 +293,7 @@ pub fn get_generic_id_and_type(tipo: &Type, param: &Type) -> Vec<(u64, Rc<Type>)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lookup_data_type_by_tipo(
|
pub fn lookup_data_type_by_tipo(
|
||||||
data_types: &IndexMap<DataTypeKey, &TypedDataType>,
|
data_types: &IndexMap<&DataTypeKey, &TypedDataType>,
|
||||||
tipo: &Type,
|
tipo: &Type,
|
||||||
) -> Option<DataType<Rc<Type>>> {
|
) -> Option<DataType<Rc<Type>>> {
|
||||||
match tipo {
|
match tipo {
|
||||||
|
@ -365,7 +346,8 @@ pub fn get_arg_type_name(tipo: &Type) -> String {
|
||||||
|
|
||||||
pub fn convert_opaque_type(
|
pub fn convert_opaque_type(
|
||||||
t: &Rc<Type>,
|
t: &Rc<Type>,
|
||||||
data_types: &IndexMap<DataTypeKey, &TypedDataType>,
|
data_types: &IndexMap<&DataTypeKey, &TypedDataType>,
|
||||||
|
deep: bool,
|
||||||
) -> Rc<Type> {
|
) -> Rc<Type> {
|
||||||
if check_replaceable_opaque_type(t, data_types) && matches!(t.as_ref(), Type::App { .. }) {
|
if check_replaceable_opaque_type(t, data_types) && matches!(t.as_ref(), Type::App { .. }) {
|
||||||
let data_type = lookup_data_type_by_tipo(data_types, t).unwrap();
|
let data_type = lookup_data_type_by_tipo(data_types, t).unwrap();
|
||||||
|
@ -382,7 +364,11 @@ pub fn convert_opaque_type(
|
||||||
|
|
||||||
let mono_type = find_and_replace_generics(generic_type, &mono_types);
|
let mono_type = find_and_replace_generics(generic_type, &mono_types);
|
||||||
|
|
||||||
convert_opaque_type(&mono_type, data_types)
|
if deep {
|
||||||
|
convert_opaque_type(&mono_type, data_types, deep)
|
||||||
|
} else {
|
||||||
|
mono_type
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
match t.as_ref() {
|
match t.as_ref() {
|
||||||
Type::App {
|
Type::App {
|
||||||
|
@ -393,7 +379,7 @@ pub fn convert_opaque_type(
|
||||||
} => {
|
} => {
|
||||||
let mut new_args = vec![];
|
let mut new_args = vec![];
|
||||||
for arg in args {
|
for arg in args {
|
||||||
let arg = convert_opaque_type(arg, data_types);
|
let arg = convert_opaque_type(arg, data_types, deep);
|
||||||
new_args.push(arg);
|
new_args.push(arg);
|
||||||
}
|
}
|
||||||
Type::App {
|
Type::App {
|
||||||
|
@ -407,11 +393,11 @@ pub fn convert_opaque_type(
|
||||||
Type::Fn { args, ret } => {
|
Type::Fn { args, ret } => {
|
||||||
let mut new_args = vec![];
|
let mut new_args = vec![];
|
||||||
for arg in args {
|
for arg in args {
|
||||||
let arg = convert_opaque_type(arg, data_types);
|
let arg = convert_opaque_type(arg, data_types, deep);
|
||||||
new_args.push(arg);
|
new_args.push(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
let ret = convert_opaque_type(ret, data_types);
|
let ret = convert_opaque_type(ret, data_types, deep);
|
||||||
|
|
||||||
Type::Fn {
|
Type::Fn {
|
||||||
args: new_args,
|
args: new_args,
|
||||||
|
@ -421,7 +407,7 @@ pub fn convert_opaque_type(
|
||||||
}
|
}
|
||||||
Type::Var { tipo: var_tipo } => {
|
Type::Var { tipo: var_tipo } => {
|
||||||
if let TypeVar::Link { tipo } = &var_tipo.borrow().clone() {
|
if let TypeVar::Link { tipo } = &var_tipo.borrow().clone() {
|
||||||
convert_opaque_type(tipo, data_types)
|
convert_opaque_type(tipo, data_types, deep)
|
||||||
} else {
|
} else {
|
||||||
t.clone()
|
t.clone()
|
||||||
}
|
}
|
||||||
|
@ -429,7 +415,7 @@ pub fn convert_opaque_type(
|
||||||
Type::Tuple { elems } => {
|
Type::Tuple { elems } => {
|
||||||
let mut new_elems = vec![];
|
let mut new_elems = vec![];
|
||||||
for arg in elems {
|
for arg in elems {
|
||||||
let arg = convert_opaque_type(arg, data_types);
|
let arg = convert_opaque_type(arg, data_types, deep);
|
||||||
new_elems.push(arg);
|
new_elems.push(arg);
|
||||||
}
|
}
|
||||||
Type::Tuple { elems: new_elems }.into()
|
Type::Tuple { elems: new_elems }.into()
|
||||||
|
@ -439,8 +425,8 @@ pub fn convert_opaque_type(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_replaceable_opaque_type(
|
pub fn check_replaceable_opaque_type(
|
||||||
t: &Rc<Type>,
|
t: &Type,
|
||||||
data_types: &IndexMap<DataTypeKey, &TypedDataType>,
|
data_types: &IndexMap<&DataTypeKey, &TypedDataType>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let data_type = lookup_data_type_by_tipo(data_types, t);
|
let data_type = lookup_data_type_by_tipo(data_types, t);
|
||||||
|
|
||||||
|
@ -636,7 +622,7 @@ pub fn monomorphize(air_tree: &mut AirTree, mono_types: &IndexMap<u64, Rc<Type>>
|
||||||
|
|
||||||
pub fn erase_opaque_type_operations(
|
pub fn erase_opaque_type_operations(
|
||||||
air_tree: &mut AirTree,
|
air_tree: &mut AirTree,
|
||||||
data_types: &IndexMap<DataTypeKey, &TypedDataType>,
|
data_types: &IndexMap<&DataTypeKey, &TypedDataType>,
|
||||||
) {
|
) {
|
||||||
if let AirTree::Constr { tipo, args, .. } = air_tree {
|
if let AirTree::Constr { tipo, args, .. } = air_tree {
|
||||||
if check_replaceable_opaque_type(tipo, data_types) {
|
if check_replaceable_opaque_type(tipo, data_types) {
|
||||||
|
@ -652,7 +638,7 @@ pub fn erase_opaque_type_operations(
|
||||||
let mut held_types = air_tree.mut_held_types();
|
let mut held_types = air_tree.mut_held_types();
|
||||||
|
|
||||||
while let Some(tipo) = held_types.pop() {
|
while let Some(tipo) = held_types.pop() {
|
||||||
*tipo = convert_opaque_type(tipo, data_types);
|
*tipo = convert_opaque_type(tipo, data_types, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -917,7 +903,7 @@ pub fn modify_cyclic_calls(
|
||||||
|
|
||||||
pub fn pattern_has_conditions(
|
pub fn pattern_has_conditions(
|
||||||
pattern: &TypedPattern,
|
pattern: &TypedPattern,
|
||||||
data_types: &IndexMap<DataTypeKey, &TypedDataType>,
|
data_types: &IndexMap<&DataTypeKey, &TypedDataType>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match pattern {
|
match pattern {
|
||||||
Pattern::List { .. } | Pattern::Int { .. } => true,
|
Pattern::List { .. } | Pattern::Int { .. } => true,
|
||||||
|
@ -943,7 +929,7 @@ pub fn pattern_has_conditions(
|
||||||
// TODO: write some tests
|
// TODO: write some tests
|
||||||
pub fn rearrange_list_clauses(
|
pub fn rearrange_list_clauses(
|
||||||
clauses: Vec<TypedClause>,
|
clauses: Vec<TypedClause>,
|
||||||
data_types: &IndexMap<DataTypeKey, &TypedDataType>,
|
data_types: &IndexMap<&DataTypeKey, &TypedDataType>,
|
||||||
) -> Vec<TypedClause> {
|
) -> Vec<TypedClause> {
|
||||||
let mut sorted_clauses = clauses;
|
let mut sorted_clauses = clauses;
|
||||||
|
|
||||||
|
@ -1181,7 +1167,7 @@ pub fn find_list_clause_or_default_first(clauses: &[TypedClause]) -> &TypedClaus
|
||||||
.unwrap_or(&clauses[0])
|
.unwrap_or(&clauses[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn convert_data_to_type(term: Term<Name>, field_type: &Rc<Type>) -> Term<Name> {
|
pub fn convert_data_to_type(term: Term<Name>, field_type: &Type) -> Term<Name> {
|
||||||
if field_type.is_int() {
|
if field_type.is_int() {
|
||||||
Term::un_i_data().apply(term)
|
Term::un_i_data().apply(term)
|
||||||
} else if field_type.is_bytearray() {
|
} else if field_type.is_bytearray() {
|
||||||
|
@ -1222,7 +1208,7 @@ pub fn convert_data_to_type(term: Term<Name>, field_type: &Rc<Type>) -> Term<Nam
|
||||||
|
|
||||||
pub fn convert_data_to_type_debug(
|
pub fn convert_data_to_type_debug(
|
||||||
term: Term<Name>,
|
term: Term<Name>,
|
||||||
field_type: &Rc<Type>,
|
field_type: &Type,
|
||||||
error_term: Term<Name>,
|
error_term: Term<Name>,
|
||||||
) -> Term<Name> {
|
) -> Term<Name> {
|
||||||
if field_type.is_int() {
|
if field_type.is_int() {
|
||||||
|
@ -1955,9 +1941,9 @@ pub fn extract_constant(term: &Term<Name>) -> Option<Rc<UplcConstant>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_src_code_by_span(
|
pub fn get_src_code_by_span(
|
||||||
module_name: &String,
|
module_name: &str,
|
||||||
span: &Span,
|
span: &Span,
|
||||||
module_src: &IndexMap<String, (String, LineNumbers)>,
|
module_src: &IndexMap<&str, &(String, LineNumbers)>,
|
||||||
) -> String {
|
) -> String {
|
||||||
let (src, _) = module_src
|
let (src, _) = module_src
|
||||||
.get(module_name)
|
.get(module_name)
|
||||||
|
@ -1969,9 +1955,9 @@ pub fn get_src_code_by_span(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_line_columns_by_span(
|
pub fn get_line_columns_by_span(
|
||||||
module_name: &String,
|
module_name: &str,
|
||||||
span: &Span,
|
span: &Span,
|
||||||
module_src: &IndexMap<String, (String, LineNumbers)>,
|
module_src: &IndexMap<&str, &(String, LineNumbers)>,
|
||||||
) -> LineColumn {
|
) -> LineColumn {
|
||||||
let (_, lines) = module_src
|
let (_, lines) = module_src
|
||||||
.get(module_name)
|
.get(module_name)
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
---
|
||||||
|
source: crates/aiken-lang/src/parser/definition/test.rs
|
||||||
|
description: "Code:\n\ntest foo(x via f, y via g) {\n True\n}\n"
|
||||||
|
---
|
||||||
|
Test(
|
||||||
|
Function {
|
||||||
|
arguments: [
|
||||||
|
ArgVia {
|
||||||
|
arg_name: Named {
|
||||||
|
name: "x",
|
||||||
|
label: "x",
|
||||||
|
location: 9..10,
|
||||||
|
is_validator_param: false,
|
||||||
|
},
|
||||||
|
location: 9..10,
|
||||||
|
via: Var {
|
||||||
|
location: 15..16,
|
||||||
|
name: "f",
|
||||||
|
},
|
||||||
|
tipo: (),
|
||||||
|
annotation: None,
|
||||||
|
},
|
||||||
|
ArgVia {
|
||||||
|
arg_name: Named {
|
||||||
|
name: "y",
|
||||||
|
label: "y",
|
||||||
|
location: 18..19,
|
||||||
|
is_validator_param: false,
|
||||||
|
},
|
||||||
|
location: 18..19,
|
||||||
|
via: Var {
|
||||||
|
location: 24..25,
|
||||||
|
name: "g",
|
||||||
|
},
|
||||||
|
tipo: (),
|
||||||
|
annotation: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
body: Var {
|
||||||
|
location: 33..37,
|
||||||
|
name: "True",
|
||||||
|
},
|
||||||
|
doc: None,
|
||||||
|
location: 0..26,
|
||||||
|
name: "foo",
|
||||||
|
public: false,
|
||||||
|
return_annotation: Some(
|
||||||
|
Constructor {
|
||||||
|
location: 0..39,
|
||||||
|
module: None,
|
||||||
|
name: "Bool",
|
||||||
|
arguments: [],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
return_type: (),
|
||||||
|
end_position: 38,
|
||||||
|
can_error: false,
|
||||||
|
},
|
||||||
|
)
|
|
@ -0,0 +1,48 @@
|
||||||
|
---
|
||||||
|
source: crates/aiken-lang/src/parser/definition/test.rs
|
||||||
|
description: "Code:\n\ntest foo(x via fuzz.any_int) {\n True\n}\n"
|
||||||
|
---
|
||||||
|
Test(
|
||||||
|
Function {
|
||||||
|
arguments: [
|
||||||
|
ArgVia {
|
||||||
|
arg_name: Named {
|
||||||
|
name: "x",
|
||||||
|
label: "x",
|
||||||
|
location: 9..10,
|
||||||
|
is_validator_param: false,
|
||||||
|
},
|
||||||
|
location: 9..10,
|
||||||
|
via: FieldAccess {
|
||||||
|
location: 15..27,
|
||||||
|
label: "any_int",
|
||||||
|
container: Var {
|
||||||
|
location: 15..19,
|
||||||
|
name: "fuzz",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tipo: (),
|
||||||
|
annotation: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
body: Var {
|
||||||
|
location: 35..39,
|
||||||
|
name: "True",
|
||||||
|
},
|
||||||
|
doc: None,
|
||||||
|
location: 0..28,
|
||||||
|
name: "foo",
|
||||||
|
public: false,
|
||||||
|
return_annotation: Some(
|
||||||
|
Constructor {
|
||||||
|
location: 0..41,
|
||||||
|
module: None,
|
||||||
|
name: "Bool",
|
||||||
|
arguments: [],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
return_type: (),
|
||||||
|
end_position: 40,
|
||||||
|
can_error: false,
|
||||||
|
},
|
||||||
|
)
|
|
@ -0,0 +1,55 @@
|
||||||
|
---
|
||||||
|
source: crates/aiken-lang/src/parser/definition/test.rs
|
||||||
|
description: "Code:\n\ntest foo(x: Int via foo()) {\n True\n}\n"
|
||||||
|
---
|
||||||
|
Test(
|
||||||
|
Function {
|
||||||
|
arguments: [
|
||||||
|
ArgVia {
|
||||||
|
arg_name: Named {
|
||||||
|
name: "x",
|
||||||
|
label: "x",
|
||||||
|
location: 9..10,
|
||||||
|
is_validator_param: false,
|
||||||
|
},
|
||||||
|
location: 9..15,
|
||||||
|
via: Call {
|
||||||
|
arguments: [],
|
||||||
|
fun: Var {
|
||||||
|
location: 20..23,
|
||||||
|
name: "foo",
|
||||||
|
},
|
||||||
|
location: 20..25,
|
||||||
|
},
|
||||||
|
tipo: (),
|
||||||
|
annotation: Some(
|
||||||
|
Constructor {
|
||||||
|
location: 12..15,
|
||||||
|
module: None,
|
||||||
|
name: "Int",
|
||||||
|
arguments: [],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
body: Var {
|
||||||
|
location: 33..37,
|
||||||
|
name: "True",
|
||||||
|
},
|
||||||
|
doc: None,
|
||||||
|
location: 0..26,
|
||||||
|
name: "foo",
|
||||||
|
public: false,
|
||||||
|
return_annotation: Some(
|
||||||
|
Constructor {
|
||||||
|
location: 0..39,
|
||||||
|
module: None,
|
||||||
|
name: "Bool",
|
||||||
|
arguments: [],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
return_type: (),
|
||||||
|
end_position: 38,
|
||||||
|
can_error: false,
|
||||||
|
},
|
||||||
|
)
|
|
@ -0,0 +1,28 @@
|
||||||
|
---
|
||||||
|
source: crates/aiken-lang/src/parser/definition/test.rs
|
||||||
|
description: "Code:\n\ntest foo() {\n True\n}\n"
|
||||||
|
---
|
||||||
|
Test(
|
||||||
|
Function {
|
||||||
|
arguments: [],
|
||||||
|
body: Var {
|
||||||
|
location: 17..21,
|
||||||
|
name: "True",
|
||||||
|
},
|
||||||
|
doc: None,
|
||||||
|
location: 0..10,
|
||||||
|
name: "foo",
|
||||||
|
public: false,
|
||||||
|
return_annotation: Some(
|
||||||
|
Constructor {
|
||||||
|
location: 0..23,
|
||||||
|
module: None,
|
||||||
|
name: "Bool",
|
||||||
|
arguments: [],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
return_type: (),
|
||||||
|
end_position: 22,
|
||||||
|
can_error: false,
|
||||||
|
},
|
||||||
|
)
|
|
@ -37,7 +37,14 @@ Test(
|
||||||
location: 0..26,
|
location: 0..26,
|
||||||
name: "invalid_inputs",
|
name: "invalid_inputs",
|
||||||
public: false,
|
public: false,
|
||||||
return_annotation: None,
|
return_annotation: Some(
|
||||||
|
Constructor {
|
||||||
|
location: 0..61,
|
||||||
|
module: None,
|
||||||
|
name: "Bool",
|
||||||
|
arguments: [],
|
||||||
|
},
|
||||||
|
),
|
||||||
return_type: (),
|
return_type: (),
|
||||||
end_position: 60,
|
end_position: 60,
|
||||||
can_error: true,
|
can_error: true,
|
||||||
|
|
|
@ -3,7 +3,13 @@ use chumsky::prelude::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
ast,
|
ast,
|
||||||
expr::UntypedExpr,
|
expr::UntypedExpr,
|
||||||
parser::{error::ParseError, expr, token::Token},
|
parser::{
|
||||||
|
annotation,
|
||||||
|
chain::{call::parser as call, field_access, tuple_index::parser as tuple_index, Chain},
|
||||||
|
error::ParseError,
|
||||||
|
expr::{self, var},
|
||||||
|
token::Token,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
|
pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
|
||||||
|
@ -13,8 +19,12 @@ pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError
|
||||||
.or_not()
|
.or_not()
|
||||||
.then_ignore(just(Token::Test))
|
.then_ignore(just(Token::Test))
|
||||||
.then(select! {Token::Name {name} => name})
|
.then(select! {Token::Name {name} => name})
|
||||||
.then_ignore(just(Token::LeftParen))
|
.then(
|
||||||
.then_ignore(just(Token::RightParen))
|
via()
|
||||||
|
.separated_by(just(Token::Comma))
|
||||||
|
.allow_trailing()
|
||||||
|
.delimited_by(just(Token::LeftParen), just(Token::RightParen)),
|
||||||
|
)
|
||||||
.then(just(Token::Fail).ignored().or_not())
|
.then(just(Token::Fail).ignored().or_not())
|
||||||
.map_with_span(|name, span| (name, span))
|
.map_with_span(|name, span| (name, span))
|
||||||
.then(
|
.then(
|
||||||
|
@ -22,26 +32,88 @@ pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError
|
||||||
.or_not()
|
.or_not()
|
||||||
.delimited_by(just(Token::LeftBrace), just(Token::RightBrace)),
|
.delimited_by(just(Token::LeftBrace), just(Token::RightBrace)),
|
||||||
)
|
)
|
||||||
.map_with_span(|((((old_fail, name), fail), span_end), body), span| {
|
.map_with_span(
|
||||||
ast::UntypedDefinition::Test(ast::Function {
|
|(((((old_fail, name), arguments), fail), span_end), body), span| {
|
||||||
arguments: vec![],
|
ast::UntypedDefinition::Test(ast::Function {
|
||||||
body: body.unwrap_or_else(|| UntypedExpr::todo(None, span)),
|
arguments,
|
||||||
doc: None,
|
body: body.unwrap_or_else(|| UntypedExpr::todo(None, span)),
|
||||||
location: span_end,
|
doc: None,
|
||||||
end_position: span.end - 1,
|
location: span_end,
|
||||||
|
end_position: span.end - 1,
|
||||||
|
name,
|
||||||
|
public: false,
|
||||||
|
return_annotation: Some(ast::Annotation::boolean(span)),
|
||||||
|
return_type: (),
|
||||||
|
can_error: fail.is_some() || old_fail.is_some(),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn via() -> impl Parser<Token, ast::UntypedArgVia, Error = ParseError> {
|
||||||
|
choice((
|
||||||
|
select! {Token::DiscardName {name} => name}.map_with_span(|name, span| {
|
||||||
|
ast::ArgName::Discarded {
|
||||||
|
label: name.clone(),
|
||||||
name,
|
name,
|
||||||
public: false,
|
location: span,
|
||||||
return_annotation: None,
|
}
|
||||||
return_type: (),
|
}),
|
||||||
can_error: fail.is_some() || old_fail.is_some(),
|
select! {Token::Name {name} => name}.map_with_span(move |name, location| {
|
||||||
|
ast::ArgName::Named {
|
||||||
|
label: name.clone(),
|
||||||
|
name,
|
||||||
|
location,
|
||||||
|
is_validator_param: false,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
.then(just(Token::Colon).ignore_then(annotation()).or_not())
|
||||||
|
.map_with_span(|(arg_name, annotation), location| (arg_name, annotation, location))
|
||||||
|
.then_ignore(just(Token::Via))
|
||||||
|
.then(fuzzer())
|
||||||
|
.map(|((arg_name, annotation, location), via)| ast::ArgVia {
|
||||||
|
arg_name,
|
||||||
|
via,
|
||||||
|
annotation,
|
||||||
|
tipo: (),
|
||||||
|
location,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fuzzer<'a>() -> impl Parser<Token, UntypedExpr, Error = ParseError> + 'a {
|
||||||
|
recursive(|expression| {
|
||||||
|
let chain = choice((
|
||||||
|
tuple_index(),
|
||||||
|
field_access::parser(),
|
||||||
|
call(expression.clone()),
|
||||||
|
));
|
||||||
|
|
||||||
|
var()
|
||||||
|
.then(chain.repeated())
|
||||||
|
.foldl(|expr, chain| match chain {
|
||||||
|
Chain::Call(args, span) => expr.call(args, span),
|
||||||
|
Chain::FieldAccess(label, span) => expr.field_access(label, span),
|
||||||
|
Chain::TupleIndex(index, span) => expr.tuple_index(index, span),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::assert_definition;
|
use crate::assert_definition;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn def_test() {
|
||||||
|
assert_definition!(
|
||||||
|
r#"
|
||||||
|
test foo() {
|
||||||
|
True
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn def_test_fail() {
|
fn def_test_fail() {
|
||||||
assert_definition!(
|
assert_definition!(
|
||||||
|
@ -54,4 +126,37 @@ mod tests {
|
||||||
"#
|
"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn def_property_test() {
|
||||||
|
assert_definition!(
|
||||||
|
r#"
|
||||||
|
test foo(x via fuzz.any_int) {
|
||||||
|
True
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn def_invalid_property_test() {
|
||||||
|
assert_definition!(
|
||||||
|
r#"
|
||||||
|
test foo(x via f, y via g) {
|
||||||
|
True
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn def_property_test_annotated_fuzzer() {
|
||||||
|
assert_definition!(
|
||||||
|
r#"
|
||||||
|
test foo(x: Int via foo()) {
|
||||||
|
True
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -187,6 +187,11 @@ pub fn parser(
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::assert_expr;
|
use crate::assert_expr;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn record_enum() {
|
||||||
|
assert_expr!(r#"Winter"#);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn record_create_labeled() {
|
fn record_create_labeled() {
|
||||||
assert_expr!(r#"User { name: "Aiken", age, thing: 2 }"#);
|
assert_expr!(r#"User { name: "Aiken", age, thing: 2 }"#);
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
source: crates/aiken-lang/src/parser/expr/record.rs
|
||||||
|
description: "Code:\n\nWinter"
|
||||||
|
---
|
||||||
|
Var {
|
||||||
|
location: 0..6,
|
||||||
|
name: "Winter",
|
||||||
|
}
|
|
@ -240,6 +240,7 @@ pub fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = ParseError> {
|
||||||
"type" => Token::Type,
|
"type" => Token::Type,
|
||||||
"when" => Token::When,
|
"when" => Token::When,
|
||||||
"validator" => Token::Validator,
|
"validator" => Token::Validator,
|
||||||
|
"via" => Token::Via,
|
||||||
_ => {
|
_ => {
|
||||||
if s.chars().next().map_or(false, |c| c.is_uppercase()) {
|
if s.chars().next().map_or(false, |c| c.is_uppercase()) {
|
||||||
Token::UpName {
|
Token::UpName {
|
||||||
|
|
|
@ -89,6 +89,7 @@ pub enum Token {
|
||||||
When,
|
When,
|
||||||
Trace,
|
Trace,
|
||||||
Validator,
|
Validator,
|
||||||
|
Via,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Token {
|
impl fmt::Display for Token {
|
||||||
|
@ -176,6 +177,7 @@ impl fmt::Display for Token {
|
||||||
Token::Test => "test",
|
Token::Test => "test",
|
||||||
Token::Fail => "fail",
|
Token::Fail => "fail",
|
||||||
Token::Validator => "validator",
|
Token::Validator => "validator",
|
||||||
|
Token::Via => "via",
|
||||||
};
|
};
|
||||||
write!(f, "\"{s}\"")
|
write!(f, "\"{s}\"")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1202,6 +1202,113 @@ fn pipe_with_wrong_type_and_full_args() {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fuzzer_ok_basic() {
|
||||||
|
let source_code = r#"
|
||||||
|
fn int() -> Fuzzer<Int> { todo }
|
||||||
|
|
||||||
|
test prop(n via int()) { todo }
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert!(check(parse(source_code)).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fuzzer_ok_explicit() {
|
||||||
|
let source_code = r#"
|
||||||
|
fn int(prng: PRNG) -> Option<(PRNG, Int)> { todo }
|
||||||
|
|
||||||
|
test prop(n via int) { todo }
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert!(check(parse(source_code)).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fuzzer_ok_list() {
|
||||||
|
let source_code = r#"
|
||||||
|
fn int() -> Fuzzer<Int> { todo }
|
||||||
|
fn list(a: Fuzzer<a>) -> Fuzzer<List<a>> { todo }
|
||||||
|
|
||||||
|
test prop(xs via list(int())) { todo }
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert!(check(parse(source_code)).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fuzzer_err_unbound() {
|
||||||
|
let source_code = r#"
|
||||||
|
fn any() -> Fuzzer<a> { todo }
|
||||||
|
fn list(a: Fuzzer<a>) -> Fuzzer<List<a>> { todo }
|
||||||
|
|
||||||
|
test prop(xs via list(any())) { todo }
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
check(parse(source_code)),
|
||||||
|
Err((_, Error::GenericLeftAtBoundary { .. }))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fuzzer_err_unify_1() {
|
||||||
|
let source_code = r#"
|
||||||
|
test prop(xs via Void) { todo }
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
check(parse(source_code)),
|
||||||
|
Err((
|
||||||
|
_,
|
||||||
|
Error::CouldNotUnify {
|
||||||
|
situation: None,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fuzzer_err_unify_2() {
|
||||||
|
let source_code = r#"
|
||||||
|
fn any() -> Fuzzer<a> { todo }
|
||||||
|
test prop(xs via any) { todo }
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
check(parse(source_code)),
|
||||||
|
Err((
|
||||||
|
_,
|
||||||
|
Error::CouldNotUnify {
|
||||||
|
situation: None,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fuzzer_err_unify_3() {
|
||||||
|
let source_code = r#"
|
||||||
|
fn list(a: Fuzzer<a>) -> Fuzzer<List<a>> { todo }
|
||||||
|
fn int() -> Fuzzer<Int> { todo }
|
||||||
|
|
||||||
|
test prop(xs: Int via list(int())) { todo }
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
check(parse(source_code)),
|
||||||
|
Err((
|
||||||
|
_,
|
||||||
|
Error::CouldNotUnify {
|
||||||
|
situation: Some(UnifyErrorSituation::FuzzerAnnotationMismatch),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn utf8_hex_literal_warning() {
|
fn utf8_hex_literal_warning() {
|
||||||
let source_code = r#"
|
let source_code = r#"
|
||||||
|
|
|
@ -57,6 +57,18 @@ pub enum Type {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Type {
|
impl Type {
|
||||||
|
pub fn qualifier(&self) -> Option<(String, String)> {
|
||||||
|
match self {
|
||||||
|
Type::App { module, name, .. } => Some((module.to_string(), name.to_string())),
|
||||||
|
Type::Fn { .. } => None,
|
||||||
|
Type::Var { ref tipo } => match &*tipo.borrow() {
|
||||||
|
TypeVar::Link { ref tipo } => tipo.qualifier(),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
Type::Tuple { .. } => Some((String::new(), "Tuple".to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_result_constructor(&self) -> bool {
|
pub fn is_result_constructor(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Type::Fn { ret, .. } => ret.is_result(),
|
Type::Fn { ret, .. } => ret.is_result(),
|
||||||
|
|
|
@ -10,7 +10,7 @@ use crate::{
|
||||||
RecordConstructor, RecordConstructorArg, Span, TypeAlias, TypedDefinition, TypedPattern,
|
RecordConstructor, RecordConstructorArg, Span, TypeAlias, TypedDefinition, TypedPattern,
|
||||||
UnqualifiedImport, UntypedArg, UntypedDefinition, Use, Validator, PIPE_VARIABLE,
|
UnqualifiedImport, UntypedArg, UntypedDefinition, Use, Validator, PIPE_VARIABLE,
|
||||||
},
|
},
|
||||||
builtins::{self, function, generic_var, tuple, unbound_var},
|
builtins::{function, generic_var, tuple, unbound_var},
|
||||||
tipo::fields::FieldMap,
|
tipo::fields::FieldMap,
|
||||||
IdGenerator,
|
IdGenerator,
|
||||||
};
|
};
|
||||||
|
@ -1185,23 +1185,22 @@ impl<'a> Environment<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
Definition::Test(Function { name, location, .. }) => {
|
Definition::Test(test) => {
|
||||||
assert_unique_value_name(names, name, location)?;
|
let arguments = test
|
||||||
hydrators.insert(name.clone(), Hydrator::new());
|
.arguments
|
||||||
let arg_types = vec![];
|
.iter()
|
||||||
let return_type = builtins::bool();
|
.map(|arg| arg.clone().into())
|
||||||
self.insert_variable(
|
.collect::<Vec<_>>();
|
||||||
name.clone(),
|
|
||||||
ValueConstructorVariant::ModuleFn {
|
self.register_function(
|
||||||
name: name.clone(),
|
&test.name,
|
||||||
field_map: None,
|
&arguments,
|
||||||
module: module_name.to_owned(),
|
&test.return_annotation,
|
||||||
arity: 0,
|
module_name,
|
||||||
location: *location,
|
hydrators,
|
||||||
builtin: None,
|
names,
|
||||||
},
|
&test.location,
|
||||||
function(arg_types, return_type),
|
)?;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Definition::DataType(DataType {
|
Definition::DataType(DataType {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use super::Type;
|
use super::Type;
|
||||||
use crate::error::ExtraData;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{Annotation, BinOp, CallArg, LogicalOpChainKind, Span, UntypedPattern},
|
ast::{Annotation, BinOp, CallArg, LogicalOpChainKind, Span, UntypedPattern},
|
||||||
|
error::ExtraData,
|
||||||
expr::{self, UntypedExpr},
|
expr::{self, UntypedExpr},
|
||||||
format::Formatter,
|
format::Formatter,
|
||||||
levenshtein,
|
levenshtein,
|
||||||
|
@ -261,7 +261,9 @@ You can use '{discard}' and numbers to distinguish between similar names.
|
||||||
|
|
||||||
#[error("I found a type definition that has a function type in it. This is not allowed.\n")]
|
#[error("I found a type definition that has a function type in it. This is not allowed.\n")]
|
||||||
#[diagnostic(code("illegal::function_in_type"))]
|
#[diagnostic(code("illegal::function_in_type"))]
|
||||||
#[diagnostic(help("Data-types can't hold functions. If you want to define method-like functions, group the type definition and the methods under a common namespace in a standalone module."))]
|
#[diagnostic(help(
|
||||||
|
"Data-types can't hold functions. If you want to define method-like functions, group the type definition and the methods under a common namespace in a standalone module."
|
||||||
|
))]
|
||||||
FunctionTypeInData {
|
FunctionTypeInData {
|
||||||
#[label]
|
#[label]
|
||||||
location: Span,
|
location: Span,
|
||||||
|
@ -270,7 +272,7 @@ You can use '{discard}' and numbers to distinguish between similar names.
|
||||||
#[error("I found a type definition that has an unsupported type in it.\n")]
|
#[error("I found a type definition that has an unsupported type in it.\n")]
|
||||||
#[diagnostic(code("illegal::type_in_data"))]
|
#[diagnostic(code("illegal::type_in_data"))]
|
||||||
#[diagnostic(help(
|
#[diagnostic(help(
|
||||||
r#"Data-types can't contain type {type_info} because it can't be represented as PlutusData."#,
|
r#"Data-types can't contain type {type_info} because it isn't serializable into a Plutus Data. Yet, this is a strong requirement for types found in compound structures such as List or Tuples."#,
|
||||||
type_info = tipo.to_pretty(0).if_supports_color(Stdout, |s| s.red())
|
type_info = tipo.to_pretty(0).if_supports_color(Stdout, |s| s.red())
|
||||||
))]
|
))]
|
||||||
IllegalTypeInData {
|
IllegalTypeInData {
|
||||||
|
@ -477,9 +479,13 @@ If you really meant to return that last expression, try to replace it with the f
|
||||||
"I stumbled upon an invalid (non-local) clause guard '{}'.\n",
|
"I stumbled upon an invalid (non-local) clause guard '{}'.\n",
|
||||||
name.if_supports_color(Stdout, |s| s.purple())
|
name.if_supports_color(Stdout, |s| s.purple())
|
||||||
)]
|
)]
|
||||||
#[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#checking-equality-and-ordering-in-patterns"))]
|
#[diagnostic(url(
|
||||||
|
"https://aiken-lang.org/language-tour/control-flow#checking-equality-and-ordering-in-patterns"
|
||||||
|
))]
|
||||||
#[diagnostic(code("illegal::clause_guard"))]
|
#[diagnostic(code("illegal::clause_guard"))]
|
||||||
#[diagnostic(help("There are some conditions regarding what can be used in a guard. Values must be either local to the function, or defined as module constants. You can't use functions or records in there."))]
|
#[diagnostic(help(
|
||||||
|
"There are some conditions regarding what can be used in a guard. Values must be either local to the function, or defined as module constants. You can't use functions or records in there."
|
||||||
|
))]
|
||||||
NonLocalClauseGuardVariable {
|
NonLocalClauseGuardVariable {
|
||||||
#[label]
|
#[label]
|
||||||
location: Span,
|
location: Span,
|
||||||
|
@ -492,7 +498,7 @@ If you really meant to return that last expression, try to replace it with the f
|
||||||
#[diagnostic(url("https://aiken-lang.org/language-tour/primitive-types#tuples"))]
|
#[diagnostic(url("https://aiken-lang.org/language-tour/primitive-types#tuples"))]
|
||||||
#[diagnostic(code("illegal::tuple_index"))]
|
#[diagnostic(code("illegal::tuple_index"))]
|
||||||
#[diagnostic(help(
|
#[diagnostic(help(
|
||||||
r#"Because you used a tuple-index on an element, I assumed it had to be a tuple or some kind, but instead I found:
|
r#"Because you used a tuple-index on an element, I assumed it had to be a tuple but instead I found something of type:
|
||||||
|
|
||||||
╰─▶ {type_info}"#,
|
╰─▶ {type_info}"#,
|
||||||
type_info = tipo.to_pretty(0).if_supports_color(Stdout, |s| s.red())
|
type_info = tipo.to_pretty(0).if_supports_color(Stdout, |s| s.red())
|
||||||
|
@ -637,7 +643,9 @@ You can help me by providing a type-annotation for 'x', as such:
|
||||||
#[error("I almost got caught in an endless loop while inferring a recursive type.\n")]
|
#[error("I almost got caught in an endless loop while inferring a recursive type.\n")]
|
||||||
#[diagnostic(url("https://aiken-lang.org/language-tour/custom-types#type-annotations"))]
|
#[diagnostic(url("https://aiken-lang.org/language-tour/custom-types#type-annotations"))]
|
||||||
#[diagnostic(code("missing::type_annotation"))]
|
#[diagnostic(code("missing::type_annotation"))]
|
||||||
#[diagnostic(help("I have several aptitudes, but inferring recursive types isn't one them. It is still possible to define recursive types just fine, but I will need a little help in the form of type annotation to infer their types should they show up."))]
|
#[diagnostic(help(
|
||||||
|
"I have several aptitudes, but inferring recursive types isn't one them. It is still possible to define recursive types just fine, but I will need a little help in the form of type annotation to infer their types should they show up."
|
||||||
|
))]
|
||||||
RecursiveType {
|
RecursiveType {
|
||||||
#[label]
|
#[label]
|
||||||
location: Span,
|
location: Span,
|
||||||
|
@ -946,6 +954,27 @@ The best thing to do from here is to remove it."#))]
|
||||||
#[label("{} arguments", if *count < 2 { "not enough" } else { "too many" })]
|
#[label("{} arguments", if *count < 2 { "not enough" } else { "too many" })]
|
||||||
location: Span,
|
location: Span,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[error("I caught a test with too many arguments.\n")]
|
||||||
|
#[diagnostic(code("illegal::test_arity"))]
|
||||||
|
#[diagnostic(help(
|
||||||
|
"Tests are allowed to have 0 or 1 argument, but no more. Here I've found a test definition with {count} arguments. If you need to provide multiple values to a test, use a Record or a Tuple.",
|
||||||
|
))]
|
||||||
|
IncorrectTestArity {
|
||||||
|
count: usize,
|
||||||
|
#[label("too many arguments")]
|
||||||
|
location: Span,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("I choked on a generic type left in an outward-facing interface.\n")]
|
||||||
|
#[diagnostic(code("illegal::generic_in_abi"))]
|
||||||
|
#[diagnostic(help(
|
||||||
|
"Functions of the outer-most parts of a project, such as a validator or a property-based test, must be fully instantiated. That means they can no longer carry unbound generic variables. The type must be fully-known at this point since many structural validation must occur to ensure a safe boundary between the on-chain and off-chain worlds."
|
||||||
|
))]
|
||||||
|
GenericLeftAtBoundary {
|
||||||
|
#[label("unbound generic at boundary")]
|
||||||
|
location: Span,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExtraData for Error {
|
impl ExtraData for Error {
|
||||||
|
@ -997,6 +1026,8 @@ impl ExtraData for Error {
|
||||||
| Error::UnnecessarySpreadOperator { .. }
|
| Error::UnnecessarySpreadOperator { .. }
|
||||||
| Error::UpdateMultiConstructorType { .. }
|
| Error::UpdateMultiConstructorType { .. }
|
||||||
| Error::ValidatorImported { .. }
|
| Error::ValidatorImported { .. }
|
||||||
|
| Error::IncorrectTestArity { .. }
|
||||||
|
| Error::GenericLeftAtBoundary { .. }
|
||||||
| Error::ValidatorMustReturnBool { .. } => None,
|
| Error::ValidatorMustReturnBool { .. } => None,
|
||||||
|
|
||||||
Error::UnknownType { name, .. }
|
Error::UnknownType { name, .. }
|
||||||
|
@ -1194,14 +1225,14 @@ fn suggest_unify(
|
||||||
|
|
||||||
(
|
(
|
||||||
format!(
|
format!(
|
||||||
"{} - {}",
|
"{}.{{{}}}",
|
||||||
|
expected_module.if_supports_color(Stdout, |s| s.bright_blue()),
|
||||||
expected_str.if_supports_color(Stdout, |s| s.green()),
|
expected_str.if_supports_color(Stdout, |s| s.green()),
|
||||||
expected_module.if_supports_color(Stdout, |s| s.bright_blue())
|
|
||||||
),
|
),
|
||||||
format!(
|
format!(
|
||||||
"{} - {}",
|
"{}.{{{}}}",
|
||||||
|
given_module.if_supports_color(Stdout, |s| s.bright_blue()),
|
||||||
given_str.if_supports_color(Stdout, |s| s.red()),
|
given_str.if_supports_color(Stdout, |s| s.red()),
|
||||||
given_module.if_supports_color(Stdout, |s| s.bright_blue())
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1276,6 +1307,21 @@ fn suggest_unify(
|
||||||
expected,
|
expected,
|
||||||
given
|
given
|
||||||
},
|
},
|
||||||
|
Some(UnifyErrorSituation::FuzzerAnnotationMismatch) => formatdoc! {
|
||||||
|
r#"While comparing the return annotation of a Fuzzer with its actual return type, I realized that both don't match.
|
||||||
|
|
||||||
|
I am inferring the Fuzzer should return:
|
||||||
|
|
||||||
|
{}
|
||||||
|
|
||||||
|
but I found a conflicting annotation saying it returns:
|
||||||
|
|
||||||
|
{}
|
||||||
|
|
||||||
|
Either, fix (or remove) the annotation or adjust the Fuzzer to return the expected type."#,
|
||||||
|
expected,
|
||||||
|
given
|
||||||
|
},
|
||||||
None => formatdoc! {
|
None => formatdoc! {
|
||||||
r#"I am inferring the following type:
|
r#"I am inferring the following type:
|
||||||
|
|
||||||
|
@ -1665,6 +1711,8 @@ pub enum UnifyErrorSituation {
|
||||||
|
|
||||||
/// The operands of a binary operator were incorrect.
|
/// The operands of a binary operator were incorrect.
|
||||||
Operator(BinOp),
|
Operator(BinOp),
|
||||||
|
|
||||||
|
FuzzerAnnotationMismatch,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
|
|
@ -1860,7 +1860,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infer_value_constructor(
|
pub fn infer_value_constructor(
|
||||||
&mut self,
|
&mut self,
|
||||||
module: &Option<String>,
|
module: &Option<String>,
|
||||||
name: &str,
|
name: &str,
|
||||||
|
|
|
@ -1,24 +1,25 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
ast::{
|
|
||||||
ArgName, DataType, Definition, Function, Layer, ModuleConstant, ModuleKind,
|
|
||||||
RecordConstructor, RecordConstructorArg, Tracing, TypeAlias, TypedDefinition,
|
|
||||||
TypedFunction, TypedModule, UntypedDefinition, UntypedModule, Use, Validator,
|
|
||||||
},
|
|
||||||
builtins,
|
|
||||||
builtins::function,
|
|
||||||
line_numbers::LineNumbers,
|
|
||||||
IdGenerator,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
environment::{generalise, EntityKind, Environment},
|
environment::{generalise, EntityKind, Environment},
|
||||||
error::{Error, Warning},
|
error::{Error, UnifyErrorSituation, Warning},
|
||||||
expr::ExprTyper,
|
expr::ExprTyper,
|
||||||
hydrator::Hydrator,
|
hydrator::Hydrator,
|
||||||
TypeInfo, ValueConstructor, ValueConstructorVariant,
|
TypeInfo, ValueConstructor, ValueConstructorVariant,
|
||||||
};
|
};
|
||||||
|
use crate::{
|
||||||
|
ast::{
|
||||||
|
Annotation, Arg, ArgName, ArgVia, DataType, Definition, Function, Layer, ModuleConstant,
|
||||||
|
ModuleKind, RecordConstructor, RecordConstructorArg, Tracing, TypeAlias, TypedArg,
|
||||||
|
TypedDefinition, TypedFunction, TypedModule, UntypedArg, UntypedDefinition, UntypedModule,
|
||||||
|
Use, Validator,
|
||||||
|
},
|
||||||
|
builtins,
|
||||||
|
builtins::{function, fuzzer, generic_var},
|
||||||
|
expr::{TypedExpr, UntypedExpr},
|
||||||
|
line_numbers::LineNumbers,
|
||||||
|
tipo::{Span, Type, TypeVar},
|
||||||
|
IdGenerator,
|
||||||
|
};
|
||||||
|
use std::{borrow::Borrow, collections::HashMap, ops::Deref, rc::Rc};
|
||||||
|
|
||||||
impl UntypedModule {
|
impl UntypedModule {
|
||||||
pub fn infer(
|
pub fn infer(
|
||||||
|
@ -159,97 +160,14 @@ fn infer_definition(
|
||||||
tracing: Tracing,
|
tracing: Tracing,
|
||||||
) -> Result<TypedDefinition, Error> {
|
) -> Result<TypedDefinition, Error> {
|
||||||
match def {
|
match def {
|
||||||
Definition::Fn(Function {
|
Definition::Fn(f) => Ok(Definition::Fn(infer_function(
|
||||||
doc,
|
f,
|
||||||
location,
|
module_name,
|
||||||
name,
|
hydrators,
|
||||||
public,
|
environment,
|
||||||
arguments: args,
|
lines,
|
||||||
body,
|
tracing,
|
||||||
return_annotation,
|
)?)),
|
||||||
end_position,
|
|
||||||
can_error,
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
let preregistered_fn = environment
|
|
||||||
.get_variable(&name)
|
|
||||||
.expect("Could not find preregistered type for function");
|
|
||||||
|
|
||||||
let field_map = preregistered_fn.field_map().cloned();
|
|
||||||
|
|
||||||
let preregistered_type = preregistered_fn.tipo.clone();
|
|
||||||
|
|
||||||
let (args_types, return_type) = preregistered_type
|
|
||||||
.function_types()
|
|
||||||
.expect("Preregistered type for fn was not a fn");
|
|
||||||
|
|
||||||
// Infer the type using the preregistered args + return types as a starting point
|
|
||||||
let (tipo, args, body, safe_to_generalise) =
|
|
||||||
environment.in_new_scope(|environment| {
|
|
||||||
let args = args
|
|
||||||
.into_iter()
|
|
||||||
.zip(&args_types)
|
|
||||||
.map(|(arg_name, tipo)| arg_name.set_type(tipo.clone()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut expr_typer = ExprTyper::new(environment, lines, tracing);
|
|
||||||
|
|
||||||
expr_typer.hydrator = hydrators
|
|
||||||
.remove(&name)
|
|
||||||
.expect("Could not find hydrator for fn");
|
|
||||||
|
|
||||||
let (args, body) =
|
|
||||||
expr_typer.infer_fn_with_known_types(args, body, Some(return_type))?;
|
|
||||||
|
|
||||||
let args_types = args.iter().map(|a| a.tipo.clone()).collect();
|
|
||||||
|
|
||||||
let tipo = function(args_types, body.tipo());
|
|
||||||
|
|
||||||
let safe_to_generalise = !expr_typer.ungeneralised_function_used;
|
|
||||||
|
|
||||||
Ok::<_, Error>((tipo, args, body, safe_to_generalise))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Assert that the inferred type matches the type of any recursive call
|
|
||||||
environment.unify(preregistered_type, tipo.clone(), location, false)?;
|
|
||||||
|
|
||||||
// Generalise the function if safe to do so
|
|
||||||
let tipo = if safe_to_generalise {
|
|
||||||
environment.ungeneralised_functions.remove(&name);
|
|
||||||
|
|
||||||
let tipo = generalise(tipo, 0);
|
|
||||||
|
|
||||||
let module_fn = ValueConstructorVariant::ModuleFn {
|
|
||||||
name: name.clone(),
|
|
||||||
field_map,
|
|
||||||
module: module_name.to_owned(),
|
|
||||||
arity: args.len(),
|
|
||||||
location,
|
|
||||||
builtin: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
environment.insert_variable(name.clone(), module_fn, tipo.clone());
|
|
||||||
|
|
||||||
tipo
|
|
||||||
} else {
|
|
||||||
tipo
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Definition::Fn(Function {
|
|
||||||
doc,
|
|
||||||
location,
|
|
||||||
name,
|
|
||||||
public,
|
|
||||||
arguments: args,
|
|
||||||
return_annotation,
|
|
||||||
return_type: tipo
|
|
||||||
.return_type()
|
|
||||||
.expect("Could not find return type for fn"),
|
|
||||||
body,
|
|
||||||
can_error,
|
|
||||||
end_position,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
Definition::Validator(Validator {
|
Definition::Validator(Validator {
|
||||||
doc,
|
doc,
|
||||||
|
@ -412,20 +330,105 @@ fn infer_definition(
|
||||||
}
|
}
|
||||||
|
|
||||||
Definition::Test(f) => {
|
Definition::Test(f) => {
|
||||||
if let Definition::Fn(f) = infer_definition(
|
let (typed_via, annotation) = match f.arguments.first() {
|
||||||
Definition::Fn(f),
|
Some(arg) => {
|
||||||
|
if f.arguments.len() > 1 {
|
||||||
|
return Err(Error::IncorrectTestArity {
|
||||||
|
count: f.arguments.len(),
|
||||||
|
location: f.arguments.get(1).expect("arguments.len() > 1").location,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let typed_via =
|
||||||
|
ExprTyper::new(environment, lines, tracing).infer(arg.via.clone())?;
|
||||||
|
|
||||||
|
let (inferred_annotation, inner_type) =
|
||||||
|
infer_fuzzer(environment, &typed_via.tipo(), &arg.via.location())?;
|
||||||
|
|
||||||
|
// Replace the pre-registered type for the test function, to allow inferring
|
||||||
|
// the function body with the right type arguments.
|
||||||
|
let scope = environment
|
||||||
|
.scope
|
||||||
|
.get_mut(&f.name)
|
||||||
|
.expect("Could not find preregistered type for test");
|
||||||
|
if let Type::Fn { ref ret, .. } = scope.tipo.as_ref() {
|
||||||
|
scope.tipo = Rc::new(Type::Fn {
|
||||||
|
ret: ret.clone(),
|
||||||
|
args: vec![inner_type.clone()],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the annotation, if any, matches the type inferred from the
|
||||||
|
// Fuzzer.
|
||||||
|
if let Some(ref provided_annotation) = arg.annotation {
|
||||||
|
let hydrator: &mut Hydrator = hydrators.get_mut(&f.name).unwrap();
|
||||||
|
|
||||||
|
let given =
|
||||||
|
hydrator.type_from_annotation(provided_annotation, environment)?;
|
||||||
|
|
||||||
|
if !provided_annotation.is_logically_equal(&inferred_annotation) {
|
||||||
|
return Err(Error::CouldNotUnify {
|
||||||
|
location: arg.location,
|
||||||
|
expected: inner_type.clone(),
|
||||||
|
given,
|
||||||
|
situation: Some(UnifyErrorSituation::FuzzerAnnotationMismatch),
|
||||||
|
rigid_type_names: hydrator.rigid_names(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((Some((typed_via, inner_type)), Some(inferred_annotation)))
|
||||||
|
}
|
||||||
|
None => Ok((None, None)),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
let typed_f = infer_function(
|
||||||
|
f.into(),
|
||||||
module_name,
|
module_name,
|
||||||
hydrators,
|
hydrators,
|
||||||
environment,
|
environment,
|
||||||
lines,
|
lines,
|
||||||
tracing,
|
tracing,
|
||||||
)? {
|
)?;
|
||||||
environment.unify(f.return_type.clone(), builtins::bool(), f.location, false)?;
|
|
||||||
|
|
||||||
Ok(Definition::Test(f))
|
environment.unify(
|
||||||
} else {
|
typed_f.return_type.clone(),
|
||||||
unreachable!("test definition inferred as something other than a function?")
|
builtins::bool(),
|
||||||
}
|
typed_f.location,
|
||||||
|
false,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Definition::Test(Function {
|
||||||
|
doc: typed_f.doc,
|
||||||
|
location: typed_f.location,
|
||||||
|
name: typed_f.name,
|
||||||
|
public: typed_f.public,
|
||||||
|
arguments: match typed_via {
|
||||||
|
Some((via, tipo)) => {
|
||||||
|
let Arg {
|
||||||
|
arg_name, location, ..
|
||||||
|
} = typed_f
|
||||||
|
.arguments
|
||||||
|
.first()
|
||||||
|
.expect("has exactly one argument")
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
vec![ArgVia {
|
||||||
|
annotation,
|
||||||
|
arg_name,
|
||||||
|
location,
|
||||||
|
tipo,
|
||||||
|
via,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
None => vec![],
|
||||||
|
},
|
||||||
|
return_annotation: typed_f.return_annotation,
|
||||||
|
return_type: typed_f.return_type,
|
||||||
|
body: typed_f.body,
|
||||||
|
can_error: typed_f.can_error,
|
||||||
|
end_position: typed_f.end_position,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
Definition::TypeAlias(TypeAlias {
|
Definition::TypeAlias(TypeAlias {
|
||||||
|
@ -640,3 +643,204 @@ fn infer_definition(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn infer_function(
|
||||||
|
f: Function<(), UntypedExpr, UntypedArg>,
|
||||||
|
module_name: &String,
|
||||||
|
hydrators: &mut HashMap<String, Hydrator>,
|
||||||
|
environment: &mut Environment<'_>,
|
||||||
|
lines: &LineNumbers,
|
||||||
|
tracing: Tracing,
|
||||||
|
) -> Result<Function<Rc<Type>, TypedExpr, TypedArg>, Error> {
|
||||||
|
let Function {
|
||||||
|
doc,
|
||||||
|
location,
|
||||||
|
name,
|
||||||
|
public,
|
||||||
|
arguments,
|
||||||
|
body,
|
||||||
|
return_annotation,
|
||||||
|
end_position,
|
||||||
|
can_error,
|
||||||
|
..
|
||||||
|
} = f;
|
||||||
|
|
||||||
|
let preregistered_fn = environment
|
||||||
|
.get_variable(&name)
|
||||||
|
.expect("Could not find preregistered type for function");
|
||||||
|
|
||||||
|
let field_map = preregistered_fn.field_map().cloned();
|
||||||
|
|
||||||
|
let preregistered_type = preregistered_fn.tipo.clone();
|
||||||
|
|
||||||
|
let (args_types, return_type) = preregistered_type
|
||||||
|
.function_types()
|
||||||
|
.expect("Preregistered type for fn was not a fn");
|
||||||
|
|
||||||
|
// Infer the type using the preregistered args + return types as a starting point
|
||||||
|
let (tipo, arguments, body, safe_to_generalise) = environment.in_new_scope(|environment| {
|
||||||
|
let args = arguments
|
||||||
|
.into_iter()
|
||||||
|
.zip(&args_types)
|
||||||
|
.map(|(arg_name, tipo)| arg_name.set_type(tipo.clone()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut expr_typer = ExprTyper::new(environment, lines, tracing);
|
||||||
|
|
||||||
|
expr_typer.hydrator = hydrators
|
||||||
|
.remove(&name)
|
||||||
|
.expect("Could not find hydrator for fn");
|
||||||
|
|
||||||
|
let (args, body) = expr_typer.infer_fn_with_known_types(args, body, Some(return_type))?;
|
||||||
|
|
||||||
|
let args_types = args.iter().map(|a| a.tipo.clone()).collect();
|
||||||
|
|
||||||
|
let tipo = function(args_types, body.tipo());
|
||||||
|
|
||||||
|
let safe_to_generalise = !expr_typer.ungeneralised_function_used;
|
||||||
|
|
||||||
|
Ok::<_, Error>((tipo, args, body, safe_to_generalise))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Assert that the inferred type matches the type of any recursive call
|
||||||
|
environment.unify(preregistered_type, tipo.clone(), location, false)?;
|
||||||
|
|
||||||
|
// Generalise the function if safe to do so
|
||||||
|
let tipo = if safe_to_generalise {
|
||||||
|
environment.ungeneralised_functions.remove(&name);
|
||||||
|
|
||||||
|
let tipo = generalise(tipo, 0);
|
||||||
|
|
||||||
|
let module_fn = ValueConstructorVariant::ModuleFn {
|
||||||
|
name: name.clone(),
|
||||||
|
field_map,
|
||||||
|
module: module_name.to_owned(),
|
||||||
|
arity: arguments.len(),
|
||||||
|
location,
|
||||||
|
builtin: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.insert_variable(name.clone(), module_fn, tipo.clone());
|
||||||
|
|
||||||
|
tipo
|
||||||
|
} else {
|
||||||
|
tipo
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Function {
|
||||||
|
doc,
|
||||||
|
location,
|
||||||
|
name,
|
||||||
|
public,
|
||||||
|
arguments,
|
||||||
|
return_annotation,
|
||||||
|
return_type: tipo
|
||||||
|
.return_type()
|
||||||
|
.expect("Could not find return type for fn"),
|
||||||
|
body,
|
||||||
|
can_error,
|
||||||
|
end_position,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn infer_fuzzer(
|
||||||
|
environment: &mut Environment<'_>,
|
||||||
|
tipo: &Rc<Type>,
|
||||||
|
location: &Span,
|
||||||
|
) -> Result<(Annotation, Rc<Type>), Error> {
|
||||||
|
let could_not_unify = || Error::CouldNotUnify {
|
||||||
|
location: *location,
|
||||||
|
expected: fuzzer(generic_var(0)),
|
||||||
|
given: tipo.clone(),
|
||||||
|
situation: None,
|
||||||
|
rigid_type_names: HashMap::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
match tipo.borrow() {
|
||||||
|
Type::Fn { ret, .. } => match ret.borrow() {
|
||||||
|
Type::App {
|
||||||
|
module, name, args, ..
|
||||||
|
} if module.is_empty() && name == "Option" && args.len() == 1 => {
|
||||||
|
match args.first().expect("args.len() == 1").borrow() {
|
||||||
|
Type::Tuple { elems } if elems.len() == 2 => {
|
||||||
|
let wrapped = elems.get(1).expect("Tuple has two elements");
|
||||||
|
|
||||||
|
// NOTE: Although we've drilled through the Fuzzer structure to get here,
|
||||||
|
// we still need to enforce that:
|
||||||
|
//
|
||||||
|
// 1. The Fuzzer is a function with a single argument of type PRNG
|
||||||
|
// 2. It returns not only a wrapped type, but also a new PRNG
|
||||||
|
//
|
||||||
|
// All-in-all, we could bundle those verification through the
|
||||||
|
// `infer_fuzzer` function, but instead, we can also just piggyback on
|
||||||
|
// `unify` now that we have figured out the type carried by the fuzzer.
|
||||||
|
environment.unify(
|
||||||
|
tipo.clone(),
|
||||||
|
fuzzer(wrapped.clone()),
|
||||||
|
*location,
|
||||||
|
false,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok((annotate_fuzzer(wrapped, location)?, wrapped.clone()))
|
||||||
|
}
|
||||||
|
_ => Err(could_not_unify()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(could_not_unify()),
|
||||||
|
},
|
||||||
|
|
||||||
|
Type::Var { tipo } => match &*tipo.deref().borrow() {
|
||||||
|
TypeVar::Link { tipo } => infer_fuzzer(environment, tipo, location),
|
||||||
|
_ => Err(Error::GenericLeftAtBoundary {
|
||||||
|
location: *location,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
|
Type::App { .. } | Type::Tuple { .. } => Err(could_not_unify()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn annotate_fuzzer(tipo: &Type, location: &Span) -> Result<Annotation, Error> {
|
||||||
|
match tipo {
|
||||||
|
Type::App {
|
||||||
|
name, module, args, ..
|
||||||
|
} => {
|
||||||
|
let arguments = args
|
||||||
|
.iter()
|
||||||
|
.map(|arg| annotate_fuzzer(arg, location))
|
||||||
|
.collect::<Result<Vec<Annotation>, _>>()?;
|
||||||
|
Ok(Annotation::Constructor {
|
||||||
|
name: name.to_owned(),
|
||||||
|
module: if module.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(module.to_owned())
|
||||||
|
},
|
||||||
|
arguments,
|
||||||
|
location: *location,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Type::Tuple { elems } => {
|
||||||
|
let elems = elems
|
||||||
|
.iter()
|
||||||
|
.map(|arg| annotate_fuzzer(arg, location))
|
||||||
|
.collect::<Result<Vec<Annotation>, _>>()?;
|
||||||
|
Ok(Annotation::Tuple {
|
||||||
|
elems,
|
||||||
|
location: *location,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Type::Var { tipo } => match &*tipo.deref().borrow() {
|
||||||
|
TypeVar::Link { tipo } => annotate_fuzzer(tipo, location),
|
||||||
|
_ => Err(Error::GenericLeftAtBoundary {
|
||||||
|
location: *location,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Type::Fn { .. } => Err(Error::IllegalTypeInData {
|
||||||
|
location: *location,
|
||||||
|
tipo: Rc::new(tipo.clone()),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use std::{collections::HashMap, path::PathBuf};
|
|
||||||
|
|
||||||
use aiken_lang::{ast::Tracing, line_numbers::LineNumbers};
|
use aiken_lang::{ast::Tracing, line_numbers::LineNumbers};
|
||||||
use aiken_project::{config::Config, error::Error as ProjectError, module::CheckedModule, Project};
|
use aiken_project::{config::Config, error::Error as ProjectError, module::CheckedModule, Project};
|
||||||
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SourceInfo {
|
pub struct SourceInfo {
|
||||||
|
@ -30,9 +29,9 @@ impl LspProject {
|
||||||
pub fn compile(&mut self) -> Result<(), Vec<ProjectError>> {
|
pub fn compile(&mut self) -> Result<(), Vec<ProjectError>> {
|
||||||
let checkpoint = self.project.checkpoint();
|
let checkpoint = self.project.checkpoint();
|
||||||
|
|
||||||
let result = self
|
let result =
|
||||||
.project
|
self.project
|
||||||
.check(true, None, false, false, Tracing::silent());
|
.check(true, None, false, false, u32::default(), Tracing::silent());
|
||||||
|
|
||||||
self.project.restore(checkpoint);
|
self.project.restore(checkpoint);
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@ zip = "0.6.4"
|
||||||
|
|
||||||
aiken-lang = { path = "../aiken-lang", version = "1.0.24-alpha" }
|
aiken-lang = { path = "../aiken-lang", version = "1.0.24-alpha" }
|
||||||
uplc = { path = '../uplc', version = "1.0.24-alpha" }
|
uplc = { path = '../uplc', version = "1.0.24-alpha" }
|
||||||
|
num-bigint = "0.4.4"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
blst = "0.3.11"
|
blst = "0.3.11"
|
||||||
|
|
|
@ -5,7 +5,7 @@ use super::{
|
||||||
};
|
};
|
||||||
use std::{iter, ops::Deref};
|
use std::{iter, ops::Deref};
|
||||||
use uplc::{
|
use uplc::{
|
||||||
ast::{Constant, Data as UplcData, DeBruijn, Term},
|
ast::{Constant, Data as UplcData},
|
||||||
PlutusData,
|
PlutusData,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ impl Parameter {
|
||||||
pub fn validate(
|
pub fn validate(
|
||||||
&self,
|
&self,
|
||||||
definitions: &Definitions<Annotated<Schema>>,
|
definitions: &Definitions<Annotated<Schema>>,
|
||||||
term: &Term<DeBruijn>,
|
constant: &Constant,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let schema = &definitions
|
let schema = &definitions
|
||||||
.lookup(&self.schema)
|
.lookup(&self.schema)
|
||||||
|
@ -42,11 +42,7 @@ impl Parameter {
|
||||||
})?
|
})?
|
||||||
.annotated;
|
.annotated;
|
||||||
|
|
||||||
if let Term::Constant(constant) = term {
|
validate_schema(schema, definitions, constant)
|
||||||
validate_schema(schema, definitions, constant)
|
|
||||||
} else {
|
|
||||||
Err(Error::NonConstantParameter)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,9 @@ use aiken_lang::{
|
||||||
};
|
};
|
||||||
use miette::NamedSource;
|
use miette::NamedSource;
|
||||||
use serde;
|
use serde;
|
||||||
use std::{borrow::Borrow, rc::Rc};
|
use std::borrow::Borrow;
|
||||||
use uplc::{
|
use uplc::{
|
||||||
ast::{Constant, DeBruijn, Program, Term},
|
ast::{Constant, DeBruijn, Program},
|
||||||
PlutusData,
|
PlutusData,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -198,14 +198,14 @@ impl Validator {
|
||||||
pub fn apply(
|
pub fn apply(
|
||||||
self,
|
self,
|
||||||
definitions: &Definitions<Annotated<Schema>>,
|
definitions: &Definitions<Annotated<Schema>>,
|
||||||
arg: &Term<DeBruijn>,
|
arg: &PlutusData,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
match self.parameters.split_first() {
|
match self.parameters.split_first() {
|
||||||
None => Err(Error::NoParametersToApply),
|
None => Err(Error::NoParametersToApply),
|
||||||
Some((head, tail)) => {
|
Some((head, tail)) => {
|
||||||
head.validate(definitions, arg)?;
|
head.validate(definitions, &Constant::Data(arg.clone()))?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
program: self.program.apply_term(arg),
|
program: self.program.apply_data(arg.clone()),
|
||||||
parameters: tail.to_vec(),
|
parameters: tail.to_vec(),
|
||||||
..self
|
..self
|
||||||
})
|
})
|
||||||
|
@ -217,7 +217,7 @@ impl Validator {
|
||||||
&self,
|
&self,
|
||||||
definitions: &Definitions<Annotated<Schema>>,
|
definitions: &Definitions<Annotated<Schema>>,
|
||||||
ask: F,
|
ask: F,
|
||||||
) -> Result<Term<DeBruijn>, Error>
|
) -> Result<PlutusData, Error>
|
||||||
where
|
where
|
||||||
F: Fn(&Annotated<Schema>, &Definitions<Annotated<Schema>>) -> Result<PlutusData, Error>,
|
F: Fn(&Annotated<Schema>, &Definitions<Annotated<Schema>>) -> Result<PlutusData, Error>,
|
||||||
{
|
{
|
||||||
|
@ -241,7 +241,7 @@ impl Validator {
|
||||||
|
|
||||||
let data = ask(&schema, definitions)?;
|
let data = ask(&schema, definitions)?;
|
||||||
|
|
||||||
Ok(Term::Constant(Rc::new(Constant::Data(data.clone()))))
|
Ok(data.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -271,10 +271,8 @@ mod tests {
|
||||||
let mut project = TestProject::new();
|
let mut project = TestProject::new();
|
||||||
|
|
||||||
let modules = CheckedModules::singleton(project.check(project.parse(indoc::indoc! { $code })));
|
let modules = CheckedModules::singleton(project.check(project.parse(indoc::indoc! { $code })));
|
||||||
let mut generator = modules.new_generator(
|
|
||||||
&project.functions,
|
let mut generator = project.new_generator(
|
||||||
&project.data_types,
|
|
||||||
&project.module_types,
|
|
||||||
Tracing::All(TraceLevel::Verbose),
|
Tracing::All(TraceLevel::Verbose),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -621,7 +619,7 @@ mod tests {
|
||||||
fn validate_arguments_integer() {
|
fn validate_arguments_integer() {
|
||||||
let definitions = fixture_definitions();
|
let definitions = fixture_definitions();
|
||||||
|
|
||||||
let term = Term::data(uplc_ast::Data::integer(42.into()));
|
let term = Constant::Data(uplc_ast::Data::integer(42.into()));
|
||||||
|
|
||||||
let param = Parameter {
|
let param = Parameter {
|
||||||
title: None,
|
title: None,
|
||||||
|
@ -635,7 +633,7 @@ mod tests {
|
||||||
fn validate_arguments_bytestring() {
|
fn validate_arguments_bytestring() {
|
||||||
let definitions = fixture_definitions();
|
let definitions = fixture_definitions();
|
||||||
|
|
||||||
let term = Term::data(uplc_ast::Data::bytestring(vec![102, 111, 111]));
|
let term = Constant::Data(uplc_ast::Data::bytestring(vec![102, 111, 111]));
|
||||||
|
|
||||||
let param = Parameter {
|
let param = Parameter {
|
||||||
title: None,
|
title: None,
|
||||||
|
@ -664,7 +662,7 @@ mod tests {
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let term = Term::data(uplc_ast::Data::list(vec![
|
let term = Constant::Data(uplc_ast::Data::list(vec![
|
||||||
uplc_ast::Data::integer(42.into()),
|
uplc_ast::Data::integer(42.into()),
|
||||||
uplc_ast::Data::integer(14.into()),
|
uplc_ast::Data::integer(14.into()),
|
||||||
]));
|
]));
|
||||||
|
@ -693,7 +691,7 @@ mod tests {
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let term = Term::data(uplc_ast::Data::list(vec![uplc_ast::Data::bytestring(
|
let term = Constant::Data(uplc_ast::Data::list(vec![uplc_ast::Data::bytestring(
|
||||||
vec![102, 111, 111],
|
vec![102, 111, 111],
|
||||||
)]));
|
)]));
|
||||||
|
|
||||||
|
@ -725,7 +723,7 @@ mod tests {
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let term = Term::data(uplc_ast::Data::list(vec![
|
let term = Constant::Data(uplc_ast::Data::list(vec![
|
||||||
uplc_ast::Data::integer(42.into()),
|
uplc_ast::Data::integer(42.into()),
|
||||||
uplc_ast::Data::bytestring(vec![102, 111, 111]),
|
uplc_ast::Data::bytestring(vec![102, 111, 111]),
|
||||||
]));
|
]));
|
||||||
|
@ -756,7 +754,7 @@ mod tests {
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let term = Term::data(uplc_ast::Data::map(vec![(
|
let term = Constant::Data(uplc_ast::Data::map(vec![(
|
||||||
uplc_ast::Data::bytestring(vec![102, 111, 111]),
|
uplc_ast::Data::bytestring(vec![102, 111, 111]),
|
||||||
uplc_ast::Data::integer(42.into()),
|
uplc_ast::Data::integer(42.into()),
|
||||||
)]));
|
)]));
|
||||||
|
@ -772,7 +770,7 @@ mod tests {
|
||||||
|
|
||||||
let definitions = fixture_definitions();
|
let definitions = fixture_definitions();
|
||||||
|
|
||||||
let term = Term::data(uplc_ast::Data::constr(1, vec![]));
|
let term = Constant::Data(uplc_ast::Data::constr(1, vec![]));
|
||||||
|
|
||||||
let param: Parameter = schema.into();
|
let param: Parameter = schema.into();
|
||||||
|
|
||||||
|
@ -807,7 +805,7 @@ mod tests {
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let term = Term::data(uplc_ast::Data::constr(
|
let term = Constant::Data(uplc_ast::Data::constr(
|
||||||
0,
|
0,
|
||||||
vec![uplc_ast::Data::constr(0, vec![])],
|
vec![uplc_ast::Data::constr(0, vec![])],
|
||||||
));
|
));
|
||||||
|
@ -863,7 +861,7 @@ mod tests {
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let term = Term::data(uplc_ast::Data::constr(
|
let term = Constant::Data(uplc_ast::Data::constr(
|
||||||
1,
|
1,
|
||||||
vec![
|
vec![
|
||||||
uplc_ast::Data::integer(14.into()),
|
uplc_ast::Data::integer(14.into()),
|
||||||
|
|
|
@ -93,7 +93,7 @@ pub enum Error {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
src: String,
|
src: String,
|
||||||
evaluation_hint: Option<String>,
|
assertion: Option<String>,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[error(
|
#[error(
|
||||||
|
@ -125,6 +125,12 @@ pub enum Error {
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
pub fn report(&self) {
|
pub fn report(&self) {
|
||||||
|
if let Error::TestFailure { verbose, .. } = self {
|
||||||
|
if !verbose {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
println!("{self:?}")
|
println!("{self:?}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -320,49 +326,54 @@ impl Diagnostic for Error {
|
||||||
Error::Parse { error, .. } => error.kind.help(),
|
Error::Parse { error, .. } => error.kind.help(),
|
||||||
Error::Type { error, .. } => error.help(),
|
Error::Type { error, .. } => error.help(),
|
||||||
Error::StandardIo(_) => None,
|
Error::StandardIo(_) => None,
|
||||||
Error::MissingManifest { .. } => Some(Box::new("Try running `aiken new <REPOSITORY/PROJECT>` to initialise a project with an example manifest.")),
|
Error::MissingManifest { .. } => Some(Box::new(
|
||||||
|
"Try running `aiken new <REPOSITORY/PROJECT>` to initialise a project with an example manifest.",
|
||||||
|
)),
|
||||||
Error::TomlLoading { .. } => None,
|
Error::TomlLoading { .. } => None,
|
||||||
Error::Format { .. } => None,
|
Error::Format { .. } => None,
|
||||||
Error::TestFailure { evaluation_hint, .. } => match evaluation_hint {
|
Error::TestFailure { assertion, .. } => match assertion {
|
||||||
None => None,
|
None => None,
|
||||||
Some(hint) => Some(Box::new(hint.to_string()))
|
Some(hint) => Some(Box::new(hint.to_string())),
|
||||||
},
|
},
|
||||||
Error::Http(_) => None,
|
Error::Http(_) => None,
|
||||||
Error::ZipExtract(_) => None,
|
Error::ZipExtract(_) => None,
|
||||||
Error::JoinError(_) => None,
|
Error::JoinError(_) => None,
|
||||||
Error::UnknownPackageVersion{..} => Some(Box::new("Perhaps, double-check the package repository and version?")),
|
Error::UnknownPackageVersion { .. } => Some(Box::new(
|
||||||
Error::UnableToResolvePackage{..} => Some(Box::new("The network is unavailable and the package isn't in the local cache either. Try connecting to the Internet so I can look it up?")),
|
"Perhaps, double-check the package repository and version?",
|
||||||
|
)),
|
||||||
|
Error::UnableToResolvePackage { .. } => Some(Box::new(
|
||||||
|
"The network is unavailable and the package isn't in the local cache either. Try connecting to the Internet so I can look it up?",
|
||||||
|
)),
|
||||||
Error::Json(error) => Some(Box::new(format!("{error}"))),
|
Error::Json(error) => Some(Box::new(format!("{error}"))),
|
||||||
Error::MalformedStakeAddress { error } => Some(Box::new(format!("A stake address must be provided either as a base16-encoded string, or as a bech32-encoded string with the 'stake' or 'stake_test' prefix.{hint}", hint = match error {
|
Error::MalformedStakeAddress { error } => Some(Box::new(format!(
|
||||||
Some(error) => format!("\n\nHere's the error I encountered: {error}"),
|
"A stake address must be provided either as a base16-encoded string, or as a bech32-encoded string with the 'stake' or 'stake_test' prefix.{hint}",
|
||||||
None => String::new(),
|
hint = match error {
|
||||||
}))),
|
Some(error) => format!("\n\nHere's the error I encountered: {error}"),
|
||||||
Error::NoValidatorNotFound { known_validators } => {
|
None => String::new(),
|
||||||
Some(Box::new(format!(
|
}
|
||||||
"Here's a list of all validators I've found in your project. Please double-check this list against the options that you've provided:\n\n{}",
|
))),
|
||||||
known_validators
|
Error::NoValidatorNotFound { known_validators } => Some(Box::new(format!(
|
||||||
.iter()
|
"Here's a list of all validators I've found in your project. Please double-check this list against the options that you've provided:\n\n{}",
|
||||||
.map(|title| format!(
|
known_validators
|
||||||
"→ {title}",
|
.iter()
|
||||||
title = title.if_supports_color(Stdout, |s| s.purple())
|
.map(|title| format!(
|
||||||
))
|
"→ {title}",
|
||||||
.collect::<Vec<String>>()
|
title = title.if_supports_color(Stdout, |s| s.purple())
|
||||||
.join("\n")
|
))
|
||||||
)))
|
.collect::<Vec<String>>()
|
||||||
},
|
.join("\n")
|
||||||
Error::MoreThanOneValidatorFound { known_validators } => {
|
))),
|
||||||
Some(Box::new(format!(
|
Error::MoreThanOneValidatorFound { known_validators } => Some(Box::new(format!(
|
||||||
"Here's a list of all validators I've found in your project. Select one of them using the appropriate options:\n\n{}",
|
"Here's a list of all validators I've found in your project. Select one of them using the appropriate options:\n\n{}",
|
||||||
known_validators
|
known_validators
|
||||||
.iter()
|
.iter()
|
||||||
.map(|title| format!(
|
.map(|title| format!(
|
||||||
"→ {title}",
|
"→ {title}",
|
||||||
title = title.if_supports_color(Stdout, |s| s.purple())
|
title = title.if_supports_color(Stdout, |s| s.purple())
|
||||||
))
|
))
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join("\n")
|
.join("\n")
|
||||||
)))
|
))),
|
||||||
},
|
|
||||||
Error::Module(e) => e.help(),
|
Error::Module(e) => e.help(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,21 +10,34 @@ pub mod options;
|
||||||
pub mod package_name;
|
pub mod package_name;
|
||||||
pub mod paths;
|
pub mod paths;
|
||||||
pub mod pretty;
|
pub mod pretty;
|
||||||
pub mod script;
|
|
||||||
pub mod telemetry;
|
pub mod telemetry;
|
||||||
#[cfg(test)]
|
pub mod test_framework;
|
||||||
mod tests;
|
pub mod utils;
|
||||||
pub mod watch;
|
pub mod watch;
|
||||||
|
|
||||||
use crate::blueprint::{
|
#[cfg(test)]
|
||||||
definitions::Definitions,
|
mod tests;
|
||||||
schema::{Annotated, Schema},
|
|
||||||
Blueprint,
|
use crate::{
|
||||||
|
blueprint::{
|
||||||
|
definitions::Definitions,
|
||||||
|
schema::{Annotated, Schema},
|
||||||
|
Blueprint,
|
||||||
|
},
|
||||||
|
config::Config,
|
||||||
|
error::{Error, Warning},
|
||||||
|
module::{CheckedModule, CheckedModules, ParsedModule, ParsedModules},
|
||||||
|
telemetry::Event,
|
||||||
};
|
};
|
||||||
use aiken_lang::{
|
use aiken_lang::{
|
||||||
ast::{Definition, Function, ModuleKind, Tracing, TypedDataType, TypedFunction, Validator},
|
ast::{
|
||||||
|
DataTypeKey, Definition, FunctionAccessKey, ModuleKind, Tracing, TypedDataType,
|
||||||
|
TypedFunction,
|
||||||
|
},
|
||||||
builtins,
|
builtins,
|
||||||
gen_uplc::builder::{DataTypeKey, FunctionAccessKey},
|
expr::UntypedExpr,
|
||||||
|
gen_uplc::CodeGenerator,
|
||||||
|
line_numbers::LineNumbers,
|
||||||
tipo::TypeInfo,
|
tipo::TypeInfo,
|
||||||
IdGenerator,
|
IdGenerator,
|
||||||
};
|
};
|
||||||
|
@ -37,8 +50,6 @@ use pallas::ledger::{
|
||||||
primitives::babbage::{self as cardano, PolicyId},
|
primitives::babbage::{self as cardano, PolicyId},
|
||||||
traverse::ComputeHash,
|
traverse::ComputeHash,
|
||||||
};
|
};
|
||||||
|
|
||||||
use script::{EvalHint, EvalInfo, Script};
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fs::{self, File},
|
fs::{self, File},
|
||||||
|
@ -46,19 +57,12 @@ use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
use telemetry::EventListener;
|
use telemetry::EventListener;
|
||||||
|
use test_framework::{Test, TestResult};
|
||||||
use uplc::{
|
use uplc::{
|
||||||
ast::{DeBruijn, Name, Program, Term},
|
ast::{Name, Program},
|
||||||
machine::cost_model::ExBudget,
|
|
||||||
PlutusData,
|
PlutusData,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
|
||||||
config::Config,
|
|
||||||
error::{Error, Warning},
|
|
||||||
module::{CheckedModule, CheckedModules, ParsedModule, ParsedModules},
|
|
||||||
telemetry::Event,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Source {
|
pub struct Source {
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
@ -87,6 +91,7 @@ where
|
||||||
event_listener: T,
|
event_listener: T,
|
||||||
functions: IndexMap<FunctionAccessKey, TypedFunction>,
|
functions: IndexMap<FunctionAccessKey, TypedFunction>,
|
||||||
data_types: IndexMap<DataTypeKey, TypedDataType>,
|
data_types: IndexMap<DataTypeKey, TypedDataType>,
|
||||||
|
module_sources: HashMap<String, (String, LineNumbers)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Project<T>
|
impl<T> Project<T>
|
||||||
|
@ -125,9 +130,20 @@ where
|
||||||
event_listener,
|
event_listener,
|
||||||
functions,
|
functions,
|
||||||
data_types,
|
data_types,
|
||||||
|
module_sources: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_generator(&'_ self, tracing: Tracing) -> CodeGenerator<'_> {
|
||||||
|
CodeGenerator::new(
|
||||||
|
utils::indexmap::as_ref_values(&self.functions),
|
||||||
|
utils::indexmap::as_ref_values(&self.data_types),
|
||||||
|
utils::indexmap::as_str_ref_values(&self.module_types),
|
||||||
|
utils::indexmap::as_str_ref_values(&self.module_sources),
|
||||||
|
tracing,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn warnings(&mut self) -> Vec<Warning> {
|
pub fn warnings(&mut self) -> Vec<Warning> {
|
||||||
std::mem::take(&mut self.warnings)
|
std::mem::take(&mut self.warnings)
|
||||||
}
|
}
|
||||||
|
@ -210,6 +226,7 @@ where
|
||||||
match_tests: Option<Vec<String>>,
|
match_tests: Option<Vec<String>>,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
exact_match: bool,
|
exact_match: bool,
|
||||||
|
seed: u32,
|
||||||
tracing: Tracing,
|
tracing: Tracing,
|
||||||
) -> Result<(), Vec<Error>> {
|
) -> Result<(), Vec<Error>> {
|
||||||
let options = Options {
|
let options = Options {
|
||||||
|
@ -221,6 +238,7 @@ where
|
||||||
match_tests,
|
match_tests,
|
||||||
verbose,
|
verbose,
|
||||||
exact_match,
|
exact_match,
|
||||||
|
seed,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -279,12 +297,7 @@ where
|
||||||
m.attach_doc_and_module_comments();
|
m.attach_doc_and_module_comments();
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut generator = self.checked_modules.new_generator(
|
let mut generator = self.new_generator(options.tracing);
|
||||||
&self.functions,
|
|
||||||
&self.data_types,
|
|
||||||
&self.module_types,
|
|
||||||
options.tracing,
|
|
||||||
);
|
|
||||||
|
|
||||||
let blueprint = Blueprint::new(&self.config, &self.checked_modules, &mut generator)
|
let blueprint = Blueprint::new(&self.config, &self.checked_modules, &mut generator)
|
||||||
.map_err(Error::Blueprint)?;
|
.map_err(Error::Blueprint)?;
|
||||||
|
@ -311,6 +324,7 @@ where
|
||||||
match_tests,
|
match_tests,
|
||||||
verbose,
|
verbose,
|
||||||
exact_match,
|
exact_match,
|
||||||
|
seed,
|
||||||
} => {
|
} => {
|
||||||
let tests =
|
let tests =
|
||||||
self.collect_tests(verbose, match_tests, exact_match, options.tracing)?;
|
self.collect_tests(verbose, match_tests, exact_match, options.tracing)?;
|
||||||
|
@ -319,31 +333,21 @@ where
|
||||||
self.event_listener.handle_event(Event::RunningTests);
|
self.event_listener.handle_event(Event::RunningTests);
|
||||||
}
|
}
|
||||||
|
|
||||||
let results = self.eval_scripts(tests);
|
let tests = self.run_tests(tests, seed);
|
||||||
|
|
||||||
let errors: Vec<Error> = results
|
let errors: Vec<Error> = tests
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|e| {
|
.filter_map(|e| {
|
||||||
if e.success {
|
if e.is_success() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(Error::TestFailure {
|
Some(e.into_error(verbose))
|
||||||
name: e.script.name.clone(),
|
|
||||||
path: e.script.input_path.clone(),
|
|
||||||
evaluation_hint: e
|
|
||||||
.script
|
|
||||||
.evaluation_hint
|
|
||||||
.as_ref()
|
|
||||||
.map(|hint| hint.to_string()),
|
|
||||||
src: e.script.program.to_pretty(),
|
|
||||||
verbose,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
self.event_listener
|
self.event_listener
|
||||||
.handle_event(Event::FinishedTests { tests: results });
|
.handle_event(Event::FinishedTests { tests });
|
||||||
|
|
||||||
if !errors.is_empty() {
|
if !errors.is_empty() {
|
||||||
Err(errors)
|
Err(errors)
|
||||||
|
@ -444,7 +448,7 @@ where
|
||||||
&self,
|
&self,
|
||||||
title: Option<&String>,
|
title: Option<&String>,
|
||||||
ask: F,
|
ask: F,
|
||||||
) -> Result<Term<DeBruijn>, Error>
|
) -> Result<PlutusData, Error>
|
||||||
where
|
where
|
||||||
F: Fn(
|
F: Fn(
|
||||||
&Annotated<Schema>,
|
&Annotated<Schema>,
|
||||||
|
@ -461,19 +465,19 @@ where
|
||||||
|known_validators| Error::MoreThanOneValidatorFound { known_validators };
|
|known_validators| Error::MoreThanOneValidatorFound { known_validators };
|
||||||
let when_missing = |known_validators| Error::NoValidatorNotFound { known_validators };
|
let when_missing = |known_validators| Error::NoValidatorNotFound { known_validators };
|
||||||
|
|
||||||
let term = blueprint.with_validator(title, when_too_many, when_missing, |validator| {
|
let data = blueprint.with_validator(title, when_too_many, when_missing, |validator| {
|
||||||
validator
|
validator
|
||||||
.ask_next_parameter(&blueprint.definitions, &ask)
|
.ask_next_parameter(&blueprint.definitions, &ask)
|
||||||
.map_err(|e| e.into())
|
.map_err(|e| e.into())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(term)
|
Ok(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_parameter(
|
pub fn apply_parameter(
|
||||||
&self,
|
&self,
|
||||||
title: Option<&String>,
|
title: Option<&String>,
|
||||||
param: &Term<DeBruijn>,
|
param: &PlutusData,
|
||||||
) -> Result<Blueprint, Error> {
|
) -> Result<Blueprint, Error> {
|
||||||
// Read blueprint
|
// Read blueprint
|
||||||
let blueprint = File::open(self.blueprint_path())
|
let blueprint = File::open(self.blueprint_path())
|
||||||
|
@ -656,11 +660,18 @@ where
|
||||||
|
|
||||||
self.warnings.extend(type_warnings);
|
self.warnings.extend(type_warnings);
|
||||||
|
|
||||||
// Register the types from this module so they can be imported into
|
// Register module sources for an easier access later.
|
||||||
// other modules.
|
self.module_sources
|
||||||
|
.insert(name.clone(), (code.clone(), LineNumbers::new(&code)));
|
||||||
|
|
||||||
|
// Register the types from this module so they can be
|
||||||
|
// imported into other modules.
|
||||||
self.module_types
|
self.module_types
|
||||||
.insert(name.clone(), ast.type_info.clone());
|
.insert(name.clone(), ast.type_info.clone());
|
||||||
|
|
||||||
|
// Register function definitions & data-types for easier access later.
|
||||||
|
ast.register_definitions(&mut self.functions, &mut self.data_types);
|
||||||
|
|
||||||
let checked_module = CheckedModule {
|
let checked_module = CheckedModule {
|
||||||
kind,
|
kind,
|
||||||
extra,
|
extra,
|
||||||
|
@ -684,9 +695,8 @@ where
|
||||||
match_tests: Option<Vec<String>>,
|
match_tests: Option<Vec<String>>,
|
||||||
exact_match: bool,
|
exact_match: bool,
|
||||||
tracing: Tracing,
|
tracing: Tracing,
|
||||||
) -> Result<Vec<Script>, Error> {
|
) -> Result<Vec<Test>, Error> {
|
||||||
let mut scripts = Vec::new();
|
let mut scripts = Vec::new();
|
||||||
let mut testable_validators = Vec::new();
|
|
||||||
|
|
||||||
let match_tests = match_tests.map(|mt| {
|
let match_tests = match_tests.map(|mt| {
|
||||||
mt.into_iter()
|
mt.into_iter()
|
||||||
|
@ -718,161 +728,83 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
for def in checked_module.ast.definitions() {
|
for def in checked_module.ast.definitions() {
|
||||||
match def {
|
if let Definition::Test(func) = def {
|
||||||
Definition::Validator(Validator {
|
if let Some(match_tests) = &match_tests {
|
||||||
params,
|
let is_match = match_tests.iter().any(|(module, names)| {
|
||||||
fun,
|
let matched_module =
|
||||||
other_fun,
|
module.is_empty() || checked_module.name.contains(module);
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
let mut fun = fun.clone();
|
|
||||||
|
|
||||||
fun.arguments = params.clone().into_iter().chain(fun.arguments).collect();
|
let matched_name = match names {
|
||||||
|
None => true,
|
||||||
|
Some(names) => names.iter().any(|name| {
|
||||||
|
if exact_match {
|
||||||
|
name == &func.name
|
||||||
|
} else {
|
||||||
|
func.name.contains(name)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
testable_validators.push((&checked_module.name, fun));
|
matched_module && matched_name
|
||||||
|
});
|
||||||
|
|
||||||
if let Some(other) = other_fun {
|
if is_match {
|
||||||
let mut other = other.clone();
|
|
||||||
|
|
||||||
other.arguments =
|
|
||||||
params.clone().into_iter().chain(other.arguments).collect();
|
|
||||||
|
|
||||||
testable_validators.push((&checked_module.name, other));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Definition::Test(func) => {
|
|
||||||
if let Some(match_tests) = &match_tests {
|
|
||||||
let is_match = match_tests.iter().any(|(module, names)| {
|
|
||||||
let matched_module =
|
|
||||||
module.is_empty() || checked_module.name.contains(module);
|
|
||||||
|
|
||||||
let matched_name = match names {
|
|
||||||
None => true,
|
|
||||||
Some(names) => names.iter().any(|name| {
|
|
||||||
if exact_match {
|
|
||||||
name == &func.name
|
|
||||||
} else {
|
|
||||||
func.name.contains(name)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
matched_module && matched_name
|
|
||||||
});
|
|
||||||
|
|
||||||
if is_match {
|
|
||||||
scripts.push((
|
|
||||||
checked_module.input_path.clone(),
|
|
||||||
checked_module.name.clone(),
|
|
||||||
func,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
scripts.push((
|
scripts.push((
|
||||||
checked_module.input_path.clone(),
|
checked_module.input_path.clone(),
|
||||||
checked_module.name.clone(),
|
checked_module.name.clone(),
|
||||||
func,
|
func,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
scripts.push((
|
||||||
|
checked_module.input_path.clone(),
|
||||||
|
checked_module.name.clone(),
|
||||||
|
func,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut programs = Vec::new();
|
let mut generator = self.new_generator(tracing);
|
||||||
|
|
||||||
let mut generator = self.checked_modules.new_generator(
|
let mut tests = Vec::new();
|
||||||
&self.functions,
|
|
||||||
&self.data_types,
|
|
||||||
&self.module_types,
|
|
||||||
tracing,
|
|
||||||
);
|
|
||||||
|
|
||||||
for (module_name, testable_validator) in &testable_validators {
|
|
||||||
generator.insert_function(
|
|
||||||
module_name.to_string(),
|
|
||||||
testable_validator.name.clone(),
|
|
||||||
testable_validator,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (input_path, module_name, func_def) in scripts {
|
|
||||||
let Function {
|
|
||||||
name,
|
|
||||||
body,
|
|
||||||
can_error,
|
|
||||||
..
|
|
||||||
} = func_def;
|
|
||||||
|
|
||||||
|
for (input_path, module_name, test) in scripts.into_iter() {
|
||||||
if verbose {
|
if verbose {
|
||||||
self.event_listener.handle_event(Event::GeneratingUPLCFor {
|
self.event_listener.handle_event(Event::GeneratingUPLCFor {
|
||||||
name: name.clone(),
|
name: test.name.clone(),
|
||||||
path: input_path.clone(),
|
path: input_path.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let evaluation_hint = func_def.test_hint().map(|(bin_op, left_src, right_src)| {
|
tests.push(Test::from_function_definition(
|
||||||
let left = generator
|
&mut generator,
|
||||||
.clone()
|
test.to_owned(),
|
||||||
.generate_test(&left_src, &module_name)
|
|
||||||
.try_into()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let right = generator
|
|
||||||
.clone()
|
|
||||||
.generate_test(&right_src, &module_name)
|
|
||||||
.try_into()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
EvalHint {
|
|
||||||
bin_op,
|
|
||||||
left,
|
|
||||||
right,
|
|
||||||
can_error: *can_error,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let program = generator.generate_test(body, &module_name);
|
|
||||||
|
|
||||||
let script = Script::new(
|
|
||||||
input_path,
|
|
||||||
module_name,
|
module_name,
|
||||||
name.to_string(),
|
input_path,
|
||||||
*can_error,
|
));
|
||||||
program.try_into().unwrap(),
|
|
||||||
evaluation_hint,
|
|
||||||
);
|
|
||||||
|
|
||||||
programs.push(script);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(programs)
|
Ok(tests)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_scripts(&self, scripts: Vec<Script>) -> Vec<EvalInfo> {
|
fn run_tests(&self, tests: Vec<Test>, seed: u32) -> Vec<TestResult<UntypedExpr>> {
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
|
||||||
// TODO: in the future we probably just want to be able to
|
let data_types = utils::indexmap::as_ref_values(&self.data_types);
|
||||||
// tell the machine to not explode on budget consumption.
|
|
||||||
let initial_budget = ExBudget {
|
|
||||||
mem: i64::MAX,
|
|
||||||
cpu: i64::MAX,
|
|
||||||
};
|
|
||||||
|
|
||||||
scripts
|
tests
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.map(|script| {
|
.map(|test| match test {
|
||||||
let mut eval_result = script.program.clone().eval(initial_budget);
|
Test::UnitTest(unit_test) => unit_test.run(),
|
||||||
|
// TODO: Get the seed from the command-line, defaulting to a random one when not
|
||||||
EvalInfo {
|
// provided.
|
||||||
success: !eval_result.failed(script.can_error),
|
Test::PropertyTest(property_test) => property_test.run(seed),
|
||||||
script,
|
|
||||||
spent_budget: eval_result.cost(),
|
|
||||||
logs: eval_result.logs(),
|
|
||||||
output: eval_result.result().ok(),
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
.collect::<Vec<TestResult<PlutusData>>>()
|
||||||
|
.into_iter()
|
||||||
|
.map(|test| test.reify(&data_types))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,11 @@
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use aiken_lang::{
|
use aiken_lang::{
|
||||||
ast::{
|
ast::{
|
||||||
DataType, Definition, Function, Located, ModuleKind, Tracing, TypedDataType, TypedFunction,
|
DataType, Definition, Function, Located, ModuleKind, TypedModule, TypedValidator,
|
||||||
TypedModule, TypedValidator, UntypedModule, Validator,
|
UntypedModule, Validator,
|
||||||
},
|
},
|
||||||
gen_uplc::{
|
|
||||||
builder::{DataTypeKey, FunctionAccessKey},
|
|
||||||
CodeGenerator,
|
|
||||||
},
|
|
||||||
line_numbers::LineNumbers,
|
|
||||||
parser::extra::{comments_before, Comment, ModuleExtra},
|
parser::extra::{comments_before, Comment, ModuleExtra},
|
||||||
tipo::TypeInfo,
|
|
||||||
};
|
};
|
||||||
use indexmap::IndexMap;
|
|
||||||
use petgraph::{algo, graph::NodeIndex, Direction, Graph};
|
use petgraph::{algo, graph::NodeIndex, Direction, Graph};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
|
@ -353,72 +346,6 @@ impl CheckedModules {
|
||||||
.into_values()
|
.into_values()
|
||||||
.filter(|module| module.kind.is_validator())
|
.filter(|module| module.kind.is_validator())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_generator<'a>(
|
|
||||||
&'a self,
|
|
||||||
builtin_functions: &'a IndexMap<FunctionAccessKey, TypedFunction>,
|
|
||||||
builtin_data_types: &'a IndexMap<DataTypeKey, TypedDataType>,
|
|
||||||
module_types: &'a HashMap<String, TypeInfo>,
|
|
||||||
tracing: Tracing,
|
|
||||||
) -> CodeGenerator<'a> {
|
|
||||||
let mut functions = IndexMap::new();
|
|
||||||
for (k, v) in builtin_functions {
|
|
||||||
functions.insert(k.clone(), v);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut data_types = IndexMap::new();
|
|
||||||
for (k, v) in builtin_data_types {
|
|
||||||
data_types.insert(k.clone(), v);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut module_src = IndexMap::new();
|
|
||||||
|
|
||||||
for module in self.values() {
|
|
||||||
for def in module.ast.definitions() {
|
|
||||||
match def {
|
|
||||||
Definition::Fn(func) => {
|
|
||||||
functions.insert(
|
|
||||||
FunctionAccessKey {
|
|
||||||
module_name: module.name.clone(),
|
|
||||||
function_name: func.name.clone(),
|
|
||||||
},
|
|
||||||
func,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Definition::DataType(dt) => {
|
|
||||||
data_types.insert(
|
|
||||||
DataTypeKey {
|
|
||||||
module_name: module.name.clone(),
|
|
||||||
defined_type: dt.name.clone(),
|
|
||||||
},
|
|
||||||
dt,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Definition::TypeAlias(_)
|
|
||||||
| Definition::ModuleConstant(_)
|
|
||||||
| Definition::Test(_)
|
|
||||||
| Definition::Validator(_)
|
|
||||||
| Definition::Use(_) => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module_src.insert(
|
|
||||||
module.name.clone(),
|
|
||||||
(module.code.clone(), LineNumbers::new(&module.code)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut module_types_index = IndexMap::new();
|
|
||||||
module_types_index.extend(module_types);
|
|
||||||
|
|
||||||
CodeGenerator::new(
|
|
||||||
functions,
|
|
||||||
data_types,
|
|
||||||
module_types_index,
|
|
||||||
module_src,
|
|
||||||
tracing.trace_level(true),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for CheckedModules {
|
impl Deref for CheckedModules {
|
||||||
|
|
|
@ -10,6 +10,7 @@ pub enum CodeGenMode {
|
||||||
match_tests: Option<Vec<String>>,
|
match_tests: Option<Vec<String>>,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
exact_match: bool,
|
exact_match: bool,
|
||||||
|
seed: u32,
|
||||||
},
|
},
|
||||||
Build(bool),
|
Build(bool),
|
||||||
NoOp,
|
NoOp,
|
||||||
|
|
|
@ -7,8 +7,8 @@ pub fn ansi_len(s: &str) -> usize {
|
||||||
.count()
|
.count()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn len_longest_line(s: &str) -> usize {
|
pub fn len_longest_line(zero: usize, s: &str) -> usize {
|
||||||
s.lines().fold(0, |max, l| {
|
s.lines().fold(zero, |max, l| {
|
||||||
let n = ansi_len(l);
|
let n = ansi_len(l);
|
||||||
if n > max {
|
if n > max {
|
||||||
n
|
n
|
||||||
|
@ -23,7 +23,7 @@ pub fn boxed(title: &str, content: &str) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn boxed_with(title: &str, content: &str, border_style: fn(&str) -> String) -> String {
|
pub fn boxed_with(title: &str, content: &str, border_style: fn(&str) -> String) -> String {
|
||||||
let n = len_longest_line(content);
|
let n = len_longest_line(ansi_len(title) + 1, content);
|
||||||
|
|
||||||
let content = content
|
let content = content
|
||||||
.lines()
|
.lines()
|
||||||
|
@ -62,7 +62,7 @@ pub fn open_box(
|
||||||
border_style: fn(&str) -> String,
|
border_style: fn(&str) -> String,
|
||||||
) -> String {
|
) -> String {
|
||||||
let i = ansi_len(content.lines().collect::<Vec<_>>().first().unwrap());
|
let i = ansi_len(content.lines().collect::<Vec<_>>().first().unwrap());
|
||||||
let j = len_longest_line(content);
|
let j = len_longest_line(ansi_len(title) + 1, content);
|
||||||
let k = ansi_len(footer);
|
let k = ansi_len(footer);
|
||||||
|
|
||||||
let content = content
|
let content = content
|
||||||
|
@ -73,15 +73,27 @@ pub fn open_box(
|
||||||
|
|
||||||
let top = format!(
|
let top = format!(
|
||||||
"{} {}",
|
"{} {}",
|
||||||
border_style("┍━"),
|
border_style(if footer.is_empty() {
|
||||||
|
"┝━"
|
||||||
|
} else {
|
||||||
|
"┍━"
|
||||||
|
}),
|
||||||
pad_right(format!("{title} "), i - 1, &border_style("━")),
|
pad_right(format!("{title} "), i - 1, &border_style("━")),
|
||||||
);
|
);
|
||||||
|
|
||||||
let bottom = format!(
|
let bottom = if footer.is_empty() {
|
||||||
"{} {}",
|
border_style("╽")
|
||||||
pad_right(border_style("┕"), j - k + 1, &border_style("━")),
|
} else {
|
||||||
footer
|
format!(
|
||||||
);
|
"{} {}",
|
||||||
|
pad_right(
|
||||||
|
border_style("┕"),
|
||||||
|
if j < k { 0 } else { j + 1 - k },
|
||||||
|
&border_style("━")
|
||||||
|
),
|
||||||
|
footer
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
format!("{top}\n{content}\n{bottom}")
|
format!("{top}\n{content}\n{bottom}")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,122 +0,0 @@
|
||||||
use crate::{pretty, ExBudget, Term};
|
|
||||||
use aiken_lang::ast::BinOp;
|
|
||||||
use std::{
|
|
||||||
fmt::{self, Display},
|
|
||||||
path::PathBuf,
|
|
||||||
};
|
|
||||||
use uplc::ast::{NamedDeBruijn, Program};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Script {
|
|
||||||
pub input_path: PathBuf,
|
|
||||||
pub module: String,
|
|
||||||
pub name: String,
|
|
||||||
pub can_error: bool,
|
|
||||||
pub program: Program<NamedDeBruijn>,
|
|
||||||
pub evaluation_hint: Option<EvalHint>,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for Script {}
|
|
||||||
|
|
||||||
impl Script {
|
|
||||||
pub fn new(
|
|
||||||
input_path: PathBuf,
|
|
||||||
module: String,
|
|
||||||
name: String,
|
|
||||||
can_error: bool,
|
|
||||||
program: Program<NamedDeBruijn>,
|
|
||||||
evaluation_hint: Option<EvalHint>,
|
|
||||||
) -> Script {
|
|
||||||
Script {
|
|
||||||
input_path,
|
|
||||||
module,
|
|
||||||
name,
|
|
||||||
program,
|
|
||||||
can_error,
|
|
||||||
evaluation_hint,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct EvalHint {
|
|
||||||
pub bin_op: BinOp,
|
|
||||||
pub left: Program<NamedDeBruijn>,
|
|
||||||
pub right: Program<NamedDeBruijn>,
|
|
||||||
pub can_error: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for EvalHint {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
let unlimited_budget = ExBudget {
|
|
||||||
mem: i64::MAX,
|
|
||||||
cpu: i64::MAX,
|
|
||||||
};
|
|
||||||
|
|
||||||
let left = pretty::boxed(
|
|
||||||
"left",
|
|
||||||
&match self.left.clone().eval(unlimited_budget).result() {
|
|
||||||
Ok(term) => format!("{term}"),
|
|
||||||
Err(err) => format!("{err}"),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
let right = pretty::boxed(
|
|
||||||
"right",
|
|
||||||
&match self.right.clone().eval(unlimited_budget).result() {
|
|
||||||
Ok(term) => format!("{term}"),
|
|
||||||
Err(err) => format!("{err}"),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
let msg = if self.can_error {
|
|
||||||
match self.bin_op {
|
|
||||||
BinOp::And => Some(format!(
|
|
||||||
"{left}\n\nand\n\n{right}\n\nare both true but shouldn't."
|
|
||||||
)),
|
|
||||||
BinOp::Or => Some(format!(
|
|
||||||
"neither\n\n{left}\n\nnor\n\n{right}\n\nshould be true."
|
|
||||||
)),
|
|
||||||
BinOp::Eq => Some(format!("{left}\n\nshould not be equal to\n\n{right}")),
|
|
||||||
BinOp::NotEq => Some(format!("{left}\n\nshould be equal to\n\n{right}")),
|
|
||||||
BinOp::LtInt => Some(format!(
|
|
||||||
"{left}\n\nshould be greater than or equal to\n\n{right}"
|
|
||||||
)),
|
|
||||||
BinOp::LtEqInt => Some(format!("{left}\n\nshould be greater than\n\n{right}")),
|
|
||||||
BinOp::GtEqInt => Some(format!(
|
|
||||||
"{left}\n\nshould be lower than or equal\n\n{right}"
|
|
||||||
)),
|
|
||||||
BinOp::GtInt => Some(format!("{left}\n\nshould be lower than\n\n{right}")),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
match self.bin_op {
|
|
||||||
BinOp::And => Some(format!("{left}\n\nand\n\n{right}\n\nshould both be true.")),
|
|
||||||
BinOp::Or => Some(format!("{left}\n\nor\n\n{right}\n\nshould be true.")),
|
|
||||||
BinOp::Eq => Some(format!("{left}\n\nshould be equal to\n\n{right}")),
|
|
||||||
BinOp::NotEq => Some(format!("{left}\n\nshould not be equal to\n\n{right}")),
|
|
||||||
BinOp::LtInt => Some(format!("{left}\n\nshould be lower than\n\n{right}")),
|
|
||||||
BinOp::LtEqInt => Some(format!(
|
|
||||||
"{left}\n\nshould be lower than or equal to\n\n{right}"
|
|
||||||
)),
|
|
||||||
BinOp::GtEqInt => Some(format!("{left}\n\nshould be greater than\n\n{right}")),
|
|
||||||
BinOp::GtInt => Some(format!(
|
|
||||||
"{left}\n\nshould be greater than or equal to\n\n{right}"
|
|
||||||
)),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.ok_or(fmt::Error)?;
|
|
||||||
|
|
||||||
f.write_str(&msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct EvalInfo {
|
|
||||||
pub success: bool,
|
|
||||||
pub script: Script,
|
|
||||||
pub spent_budget: ExBudget,
|
|
||||||
pub output: Option<Term<NamedDeBruijn>>,
|
|
||||||
pub logs: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for EvalInfo {}
|
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::pretty;
|
use crate::{
|
||||||
use crate::script::EvalInfo;
|
pretty,
|
||||||
use owo_colors::{
|
test_framework::{PropertyTestResult, TestResult, UnitTestResult},
|
||||||
OwoColorize,
|
|
||||||
Stream::{self, Stderr},
|
|
||||||
};
|
};
|
||||||
|
use aiken_lang::{expr::UntypedExpr, format::Formatter};
|
||||||
|
use owo_colors::{OwoColorize, Stream::Stderr};
|
||||||
use std::{collections::BTreeMap, fmt::Display, path::PathBuf};
|
use std::{collections::BTreeMap, fmt::Display, path::PathBuf};
|
||||||
use uplc::machine::cost_model::ExBudget;
|
use uplc::machine::cost_model::ExBudget;
|
||||||
|
|
||||||
|
@ -35,12 +35,9 @@ pub enum Event {
|
||||||
name: String,
|
name: String,
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
},
|
},
|
||||||
EvaluatingFunction {
|
|
||||||
results: Vec<EvalInfo>,
|
|
||||||
},
|
|
||||||
RunningTests,
|
RunningTests,
|
||||||
FinishedTests {
|
FinishedTests {
|
||||||
tests: Vec<EvalInfo>,
|
tests: Vec<TestResult<UntypedExpr>>,
|
||||||
},
|
},
|
||||||
WaitingForBuildDirLock,
|
WaitingForBuildDirLock,
|
||||||
ResolvingPackages {
|
ResolvingPackages {
|
||||||
|
@ -164,20 +161,6 @@ impl EventListener for Terminal {
|
||||||
name.if_supports_color(Stderr, |s| s.bright_blue()),
|
name.if_supports_color(Stderr, |s| s.bright_blue()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Event::EvaluatingFunction { results } => {
|
|
||||||
eprintln!(
|
|
||||||
"{}\n",
|
|
||||||
" Evaluating function ..."
|
|
||||||
.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
.if_supports_color(Stderr, |s| s.purple())
|
|
||||||
);
|
|
||||||
|
|
||||||
let (max_mem, max_cpu) = find_max_execution_units(&results);
|
|
||||||
|
|
||||||
for eval_info in &results {
|
|
||||||
println!(" {}", fmt_eval(eval_info, max_mem, max_cpu, Stderr))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::RunningTests => {
|
Event::RunningTests => {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{} {}\n",
|
"{} {}\n",
|
||||||
|
@ -188,21 +171,21 @@ impl EventListener for Terminal {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Event::FinishedTests { tests } => {
|
Event::FinishedTests { tests } => {
|
||||||
let (max_mem, max_cpu) = find_max_execution_units(&tests);
|
let (max_mem, max_cpu, max_iter) = find_max_execution_units(&tests);
|
||||||
|
|
||||||
for (module, infos) in &group_by_module(&tests) {
|
for (module, results) in &group_by_module(&tests) {
|
||||||
let title = module
|
let title = module
|
||||||
.if_supports_color(Stderr, |s| s.bold())
|
.if_supports_color(Stderr, |s| s.bold())
|
||||||
.if_supports_color(Stderr, |s| s.blue())
|
.if_supports_color(Stderr, |s| s.blue())
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let tests = infos
|
let tests = results
|
||||||
.iter()
|
.iter()
|
||||||
.map(|eval_info| fmt_test(eval_info, max_mem, max_cpu, true))
|
.map(|r| fmt_test(r, max_mem, max_cpu, max_iter, true))
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join("\n");
|
.join("\n");
|
||||||
|
|
||||||
let summary = fmt_test_summary(infos, true);
|
let summary = fmt_test_summary(results, true);
|
||||||
|
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{}\n",
|
"{}\n",
|
||||||
|
@ -269,81 +252,125 @@ impl EventListener for Terminal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fmt_test(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize, styled: bool) -> String {
|
fn fmt_test(
|
||||||
let EvalInfo {
|
result: &TestResult<UntypedExpr>,
|
||||||
success,
|
max_mem: usize,
|
||||||
script,
|
max_cpu: usize,
|
||||||
spent_budget,
|
max_iter: usize,
|
||||||
logs,
|
styled: bool,
|
||||||
..
|
) -> String {
|
||||||
} = eval_info;
|
// Status
|
||||||
|
let mut test = if result.is_success() {
|
||||||
let ExBudget { mem, cpu } = spent_budget;
|
pretty::style_if(styled, "PASS".to_string(), |s| {
|
||||||
let mem_pad = pretty::pad_left(mem.to_string(), max_mem, " ");
|
s.if_supports_color(Stderr, |s| s.bold())
|
||||||
let cpu_pad = pretty::pad_left(cpu.to_string(), max_cpu, " ");
|
.if_supports_color(Stderr, |s| s.green())
|
||||||
|
.to_string()
|
||||||
let test = format!(
|
})
|
||||||
"{status} [mem: {mem_unit}, cpu: {cpu_unit}] {module}",
|
|
||||||
status = if *success {
|
|
||||||
pretty::style_if(styled, "PASS".to_string(), |s| {
|
|
||||||
s.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
.if_supports_color(Stderr, |s| s.green())
|
|
||||||
.to_string()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
pretty::style_if(styled, "FAIL".to_string(), |s| {
|
|
||||||
s.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
.if_supports_color(Stderr, |s| s.red())
|
|
||||||
.to_string()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
mem_unit = pretty::style_if(styled, mem_pad, |s| s
|
|
||||||
.if_supports_color(Stderr, |s| s.cyan())
|
|
||||||
.to_string()),
|
|
||||||
cpu_unit = pretty::style_if(styled, cpu_pad, |s| s
|
|
||||||
.if_supports_color(Stderr, |s| s.cyan())
|
|
||||||
.to_string()),
|
|
||||||
module = pretty::style_if(styled, script.name.clone(), |s| s
|
|
||||||
.if_supports_color(Stderr, |s| s.bright_blue())
|
|
||||||
.to_string()),
|
|
||||||
);
|
|
||||||
|
|
||||||
let logs = if logs.is_empty() {
|
|
||||||
String::new()
|
|
||||||
} else {
|
} else {
|
||||||
logs.iter()
|
pretty::style_if(styled, "FAIL".to_string(), |s| {
|
||||||
.map(|line| {
|
s.if_supports_color(Stderr, |s| s.bold())
|
||||||
format!(
|
.if_supports_color(Stderr, |s| s.red())
|
||||||
"{arrow} {styled_line}",
|
.to_string()
|
||||||
arrow = "↳".if_supports_color(Stderr, |s| s.bright_yellow()),
|
})
|
||||||
styled_line = line
|
|
||||||
.split('\n')
|
|
||||||
.map(|l| format!("{}", l.if_supports_color(Stderr, |s| s.bright_black())))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("\n")
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("\n")
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if logs.is_empty() {
|
// Execution units / iteration steps
|
||||||
test
|
match result {
|
||||||
} else {
|
TestResult::UnitTestResult(UnitTestResult { spent_budget, .. }) => {
|
||||||
[test, logs].join("\n")
|
let ExBudget { mem, cpu } = spent_budget;
|
||||||
|
let mem_pad = pretty::pad_left(mem.to_string(), max_mem, " ");
|
||||||
|
let cpu_pad = pretty::pad_left(cpu.to_string(), max_cpu, " ");
|
||||||
|
|
||||||
|
test = format!(
|
||||||
|
"{test} [mem: {mem_unit}, cpu: {cpu_unit}]",
|
||||||
|
mem_unit = pretty::style_if(styled, mem_pad, |s| s
|
||||||
|
.if_supports_color(Stderr, |s| s.cyan())
|
||||||
|
.to_string()),
|
||||||
|
cpu_unit = pretty::style_if(styled, cpu_pad, |s| s
|
||||||
|
.if_supports_color(Stderr, |s| s.cyan())
|
||||||
|
.to_string()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
TestResult::PropertyTestResult(PropertyTestResult { iterations, .. }) => {
|
||||||
|
test = pretty::pad_right(
|
||||||
|
format!(
|
||||||
|
"{test} [after {} test{}]",
|
||||||
|
pretty::pad_left(iterations.to_string(), max_iter, " "),
|
||||||
|
if *iterations > 1 { "s" } else { "" }
|
||||||
|
),
|
||||||
|
18 + max_mem + max_cpu + max_iter,
|
||||||
|
" ",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Title
|
||||||
|
test = format!(
|
||||||
|
"{test} {title}",
|
||||||
|
title = pretty::style_if(styled, result.title().to_string(), |s| s
|
||||||
|
.if_supports_color(Stderr, |s| s.bright_blue())
|
||||||
|
.to_string())
|
||||||
|
);
|
||||||
|
|
||||||
|
// CounterExample
|
||||||
|
if let TestResult::PropertyTestResult(PropertyTestResult {
|
||||||
|
counterexample: Some(counterexample),
|
||||||
|
..
|
||||||
|
}) = result
|
||||||
|
{
|
||||||
|
test = format!(
|
||||||
|
"{test}\n{}",
|
||||||
|
pretty::open_box(
|
||||||
|
&pretty::style_if(styled, "counterexample".to_string(), |s| s
|
||||||
|
.if_supports_color(Stderr, |s| s.red())
|
||||||
|
.if_supports_color(Stderr, |s| s.bold())
|
||||||
|
.to_string()),
|
||||||
|
&Formatter::new()
|
||||||
|
.expr(counterexample, false)
|
||||||
|
.to_pretty_string(70),
|
||||||
|
"",
|
||||||
|
|s| s.red().to_string()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traces
|
||||||
|
if !result.logs().is_empty() {
|
||||||
|
test = format!(
|
||||||
|
"{test}\n{logs}",
|
||||||
|
logs = result
|
||||||
|
.logs()
|
||||||
|
.iter()
|
||||||
|
.map(|line| {
|
||||||
|
format!(
|
||||||
|
"{arrow} {styled_line}",
|
||||||
|
arrow = "↳".if_supports_color(Stderr, |s| s.bright_yellow()),
|
||||||
|
styled_line = line
|
||||||
|
.split('\n')
|
||||||
|
.map(|l| format!(
|
||||||
|
"{}",
|
||||||
|
l.if_supports_color(Stderr, |s| s.bright_black())
|
||||||
|
))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
test
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fmt_test_summary(tests: &[&EvalInfo], styled: bool) -> String {
|
fn fmt_test_summary<T>(tests: &[&TestResult<T>], styled: bool) -> String {
|
||||||
let (n_passed, n_failed) = tests
|
let (n_passed, n_failed) = tests.iter().fold((0, 0), |(n_passed, n_failed), result| {
|
||||||
.iter()
|
if result.is_success() {
|
||||||
.fold((0, 0), |(n_passed, n_failed), test_info| {
|
(n_passed + 1, n_failed)
|
||||||
if test_info.success {
|
} else {
|
||||||
(n_passed + 1, n_failed)
|
(n_passed, n_failed + 1)
|
||||||
} else {
|
}
|
||||||
(n_passed, n_failed + 1)
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
format!(
|
format!(
|
||||||
"{} | {} | {}",
|
"{} | {} | {}",
|
||||||
pretty::style_if(styled, format!("{} tests", tests.len()), |s| s
|
pretty::style_if(styled, format!("{} tests", tests.len()), |s| s
|
||||||
|
@ -360,53 +387,38 @@ fn fmt_test_summary(tests: &[&EvalInfo], styled: bool) -> String {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fmt_eval(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize, stream: Stream) -> String {
|
fn group_by_module<T>(results: &Vec<TestResult<T>>) -> BTreeMap<String, Vec<&TestResult<T>>> {
|
||||||
let EvalInfo {
|
|
||||||
output,
|
|
||||||
script,
|
|
||||||
spent_budget,
|
|
||||||
..
|
|
||||||
} = eval_info;
|
|
||||||
|
|
||||||
let ExBudget { mem, cpu } = spent_budget;
|
|
||||||
|
|
||||||
format!(
|
|
||||||
" {}::{} [mem: {}, cpu: {}]\n │\n ╰─▶ {}",
|
|
||||||
script.module.if_supports_color(stream, |s| s.blue()),
|
|
||||||
script.name.if_supports_color(stream, |s| s.bright_blue()),
|
|
||||||
pretty::pad_left(mem.to_string(), max_mem, " "),
|
|
||||||
pretty::pad_left(cpu.to_string(), max_cpu, " "),
|
|
||||||
output
|
|
||||||
.as_ref()
|
|
||||||
.map(|x| format!("{x}"))
|
|
||||||
.unwrap_or_else(|| "Error.".to_string()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn group_by_module(infos: &Vec<EvalInfo>) -> BTreeMap<String, Vec<&EvalInfo>> {
|
|
||||||
let mut modules = BTreeMap::new();
|
let mut modules = BTreeMap::new();
|
||||||
for eval_info in infos {
|
for r in results {
|
||||||
let xs: &mut Vec<&EvalInfo> = modules.entry(eval_info.script.module.clone()).or_default();
|
let xs: &mut Vec<&TestResult<_>> = modules.entry(r.module().to_string()).or_default();
|
||||||
xs.push(eval_info);
|
xs.push(r);
|
||||||
}
|
}
|
||||||
modules
|
modules
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_max_execution_units(xs: &[EvalInfo]) -> (usize, usize) {
|
fn find_max_execution_units<T>(xs: &[TestResult<T>]) -> (usize, usize, usize) {
|
||||||
let (max_mem, max_cpu) = xs.iter().fold(
|
let (max_mem, max_cpu, max_iter) =
|
||||||
(0, 0),
|
xs.iter()
|
||||||
|(max_mem, max_cpu), EvalInfo { spent_budget, .. }| {
|
.fold((0, 0, 0), |(max_mem, max_cpu, max_iter), test| match test {
|
||||||
if spent_budget.mem >= max_mem && spent_budget.cpu >= max_cpu {
|
TestResult::PropertyTestResult(PropertyTestResult { iterations, .. }) => {
|
||||||
(spent_budget.mem, spent_budget.cpu)
|
(max_mem, max_cpu, std::cmp::max(max_iter, *iterations))
|
||||||
} else if spent_budget.mem > max_mem {
|
}
|
||||||
(spent_budget.mem, max_cpu)
|
TestResult::UnitTestResult(UnitTestResult { spent_budget, .. }) => {
|
||||||
} else if spent_budget.cpu > max_cpu {
|
if spent_budget.mem >= max_mem && spent_budget.cpu >= max_cpu {
|
||||||
(max_mem, spent_budget.cpu)
|
(spent_budget.mem, spent_budget.cpu, max_iter)
|
||||||
} else {
|
} else if spent_budget.mem > max_mem {
|
||||||
(max_mem, max_cpu)
|
(spent_budget.mem, max_cpu, max_iter)
|
||||||
}
|
} else if spent_budget.cpu > max_cpu {
|
||||||
},
|
(max_mem, spent_budget.cpu, max_iter)
|
||||||
);
|
} else {
|
||||||
|
(max_mem, max_cpu, max_iter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
(max_mem.to_string().len(), max_cpu.to_string().len())
|
(
|
||||||
|
max_mem.to_string().len(),
|
||||||
|
max_cpu.to_string().len(),
|
||||||
|
max_iter.to_string().len(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,7 @@
|
||||||
|
use super::TestProject;
|
||||||
|
use crate::module::CheckedModules;
|
||||||
|
use aiken_lang::ast::{Definition, Function, TraceLevel, Tracing, TypedTest, TypedValidator};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
use aiken_lang::ast::{Definition, Function, TraceLevel, Tracing, TypedFunction, TypedValidator};
|
|
||||||
use uplc::{
|
use uplc::{
|
||||||
ast::{Constant, Data, DeBruijn, Name, Program, Term, Type},
|
ast::{Constant, Data, DeBruijn, Name, Program, Term, Type},
|
||||||
builder::{CONSTR_FIELDS_EXPOSER, CONSTR_INDEX_EXPOSER},
|
builder::{CONSTR_FIELDS_EXPOSER, CONSTR_INDEX_EXPOSER},
|
||||||
|
@ -8,12 +9,8 @@ use uplc::{
|
||||||
optimize,
|
optimize,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::module::CheckedModules;
|
|
||||||
|
|
||||||
use super::TestProject;
|
|
||||||
|
|
||||||
enum TestType {
|
enum TestType {
|
||||||
Func(TypedFunction),
|
Func(TypedTest),
|
||||||
Validator(TypedValidator),
|
Validator(TypedValidator),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,12 +19,7 @@ fn assert_uplc(source_code: &str, expected: Term<Name>, should_fail: bool) {
|
||||||
|
|
||||||
let modules = CheckedModules::singleton(project.check(project.parse(source_code)));
|
let modules = CheckedModules::singleton(project.check(project.parse(source_code)));
|
||||||
|
|
||||||
let mut generator = modules.new_generator(
|
let mut generator = project.new_generator(Tracing::All(TraceLevel::Verbose));
|
||||||
&project.functions,
|
|
||||||
&project.data_types,
|
|
||||||
&project.module_types,
|
|
||||||
Tracing::All(TraceLevel::Verbose),
|
|
||||||
);
|
|
||||||
|
|
||||||
let Some(checked_module) = modules.values().next() else {
|
let Some(checked_module) = modules.values().next() else {
|
||||||
unreachable!("There's got to be one right?")
|
unreachable!("There's got to be one right?")
|
||||||
|
@ -57,7 +49,7 @@ fn assert_uplc(source_code: &str, expected: Term<Name>, should_fail: bool) {
|
||||||
|
|
||||||
match &script.2 {
|
match &script.2 {
|
||||||
TestType::Func(Function { body: func, .. }) => {
|
TestType::Func(Function { body: func, .. }) => {
|
||||||
let program = generator.generate_test(func, &script.1);
|
let program = generator.generate_raw(func, &[], &script.1);
|
||||||
|
|
||||||
let debruijn_program: Program<DeBruijn> = program.try_into().unwrap();
|
let debruijn_program: Program<DeBruijn> = program.try_into().unwrap();
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
use std::collections::HashMap;
|
use crate::{
|
||||||
use std::path::PathBuf;
|
builtins,
|
||||||
|
module::{CheckedModule, ParsedModule},
|
||||||
|
package_name::PackageName,
|
||||||
|
utils,
|
||||||
|
};
|
||||||
use aiken_lang::{
|
use aiken_lang::{
|
||||||
ast::{ModuleKind, TraceLevel, Tracing, TypedDataType, TypedFunction},
|
ast::{
|
||||||
gen_uplc::builder::{DataTypeKey, FunctionAccessKey},
|
DataTypeKey, FunctionAccessKey, ModuleKind, TraceLevel, Tracing, TypedDataType,
|
||||||
|
TypedFunction,
|
||||||
|
},
|
||||||
|
gen_uplc::CodeGenerator,
|
||||||
|
line_numbers::LineNumbers,
|
||||||
parser,
|
parser,
|
||||||
tipo::TypeInfo,
|
tipo::TypeInfo,
|
||||||
IdGenerator,
|
IdGenerator,
|
||||||
};
|
};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
use crate::{
|
|
||||||
builtins,
|
|
||||||
module::{CheckedModule, ParsedModule},
|
|
||||||
package_name::PackageName,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod gen_uplc;
|
mod gen_uplc;
|
||||||
|
|
||||||
|
@ -24,9 +26,10 @@ mod gen_uplc;
|
||||||
pub struct TestProject {
|
pub struct TestProject {
|
||||||
pub package: PackageName,
|
pub package: PackageName,
|
||||||
pub id_gen: IdGenerator,
|
pub id_gen: IdGenerator,
|
||||||
pub module_types: HashMap<String, TypeInfo>,
|
|
||||||
pub functions: IndexMap<FunctionAccessKey, TypedFunction>,
|
pub functions: IndexMap<FunctionAccessKey, TypedFunction>,
|
||||||
pub data_types: IndexMap<DataTypeKey, TypedDataType>,
|
pub data_types: IndexMap<DataTypeKey, TypedDataType>,
|
||||||
|
pub module_types: HashMap<String, TypeInfo>,
|
||||||
|
pub module_sources: HashMap<String, (String, LineNumbers)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestProject {
|
impl TestProject {
|
||||||
|
@ -51,9 +54,20 @@ impl TestProject {
|
||||||
module_types,
|
module_types,
|
||||||
functions,
|
functions,
|
||||||
data_types,
|
data_types,
|
||||||
|
module_sources: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_generator(&'_ self, tracing: Tracing) -> CodeGenerator<'_> {
|
||||||
|
CodeGenerator::new(
|
||||||
|
utils::indexmap::as_ref_values(&self.functions),
|
||||||
|
utils::indexmap::as_ref_values(&self.data_types),
|
||||||
|
utils::indexmap::as_str_ref_values(&self.module_types),
|
||||||
|
utils::indexmap::as_str_ref_values(&self.module_sources),
|
||||||
|
tracing,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse(&self, source_code: &str) -> ParsedModule {
|
pub fn parse(&self, source_code: &str) -> ParsedModule {
|
||||||
let kind = ModuleKind::Validator;
|
let kind = ModuleKind::Validator;
|
||||||
let name = "test_module".to_owned();
|
let name = "test_module".to_owned();
|
||||||
|
@ -86,6 +100,17 @@ impl TestProject {
|
||||||
)
|
)
|
||||||
.expect("Failed to type-check module");
|
.expect("Failed to type-check module");
|
||||||
|
|
||||||
|
// Register function definitions & data-types for easier access later.
|
||||||
|
ast.register_definitions(&mut self.functions, &mut self.data_types);
|
||||||
|
|
||||||
|
// Register module sources for an easier access later.
|
||||||
|
self.module_sources.insert(
|
||||||
|
module.name.clone(),
|
||||||
|
(module.code.clone(), LineNumbers::new(&module.code)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Register the types from this module so they can be
|
||||||
|
// imported into other modules.
|
||||||
self.module_types
|
self.module_types
|
||||||
.insert(module.name.clone(), ast.type_info.clone());
|
.insert(module.name.clone(), ast.type_info.clone());
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use std::{collections::HashMap, hash::Hash};
|
||||||
|
|
||||||
|
pub fn as_ref_values<'a, K, V>(iter: &'a IndexMap<K, V>) -> IndexMap<&'a K, &'a V>
|
||||||
|
where
|
||||||
|
K: Eq + Hash + Clone + 'a,
|
||||||
|
{
|
||||||
|
let mut refs = IndexMap::new();
|
||||||
|
for (k, v) in iter {
|
||||||
|
refs.insert(k, v);
|
||||||
|
}
|
||||||
|
refs
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str_ref_values<V>(iter: &'_ HashMap<String, V>) -> IndexMap<&'_ str, &'_ V> {
|
||||||
|
let mut refs = IndexMap::new();
|
||||||
|
for (k, v) in iter {
|
||||||
|
refs.insert(k.as_str(), v);
|
||||||
|
}
|
||||||
|
refs
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
pub mod indexmap;
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{telemetry::Terminal, Project};
|
use crate::{telemetry::Terminal, Error, Project};
|
||||||
use miette::{Diagnostic, IntoDiagnostic};
|
use miette::{Diagnostic, IntoDiagnostic};
|
||||||
use notify::{Event, RecursiveMode, Watcher};
|
use notify::{Event, RecursiveMode, Watcher};
|
||||||
use owo_colors::{OwoColorize, Stream::Stderr};
|
use owo_colors::{OwoColorize, Stream::Stderr};
|
||||||
|
@ -75,7 +75,12 @@ pub fn default_filter(evt: &Event) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_project<A>(directory: Option<&Path>, deny: bool, mut action: A) -> miette::Result<()>
|
pub fn with_project<A>(
|
||||||
|
directory: Option<&Path>,
|
||||||
|
seed: u32,
|
||||||
|
deny: bool,
|
||||||
|
mut action: A,
|
||||||
|
) -> miette::Result<()>
|
||||||
where
|
where
|
||||||
A: FnMut(&mut Project<Terminal>) -> Result<(), Vec<crate::error::Error>>,
|
A: FnMut(&mut Project<Terminal>) -> Result<(), Vec<crate::error::Error>>,
|
||||||
{
|
{
|
||||||
|
@ -116,17 +121,26 @@ where
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if errs.iter().any(|e| matches!(e, Error::TestFailure { .. })) {
|
||||||
|
eprintln!(
|
||||||
|
" {}══╤══\n{} ╰─▶ use {} {} to replay",
|
||||||
|
if errs.len() > 1 { "═" } else { "" },
|
||||||
|
if errs.len() > 1 { " " } else { "" },
|
||||||
|
"--seed".if_supports_color(Stderr, |s| s.bold()),
|
||||||
|
format!("{seed}").if_supports_color(Stderr, |s| s.bold())
|
||||||
|
);
|
||||||
|
}
|
||||||
return Err(ExitFailure::into_report());
|
return Err(ExitFailure::into_report());
|
||||||
} else {
|
|
||||||
eprintln!(
|
|
||||||
"{}",
|
|
||||||
Summary {
|
|
||||||
error_count: 0,
|
|
||||||
warning_count
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eprintln!(
|
||||||
|
"{}",
|
||||||
|
Summary {
|
||||||
|
error_count: 0,
|
||||||
|
warning_count
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if warning_count > 0 && deny {
|
if warning_count > 0 && deny {
|
||||||
Err(ExitFailure::into_report())
|
Err(ExitFailure::into_report())
|
||||||
} else {
|
} else {
|
||||||
|
@ -148,6 +162,7 @@ where
|
||||||
pub fn watch_project<F, A>(
|
pub fn watch_project<F, A>(
|
||||||
directory: Option<&Path>,
|
directory: Option<&Path>,
|
||||||
filter: F,
|
filter: F,
|
||||||
|
seed: u32,
|
||||||
debounce: u32,
|
debounce: u32,
|
||||||
mut action: A,
|
mut action: A,
|
||||||
) -> miette::Result<()>
|
) -> miette::Result<()>
|
||||||
|
@ -219,7 +234,7 @@ where
|
||||||
.if_supports_color(Stderr, |s| s.bold())
|
.if_supports_color(Stderr, |s| s.bold())
|
||||||
.if_supports_color(Stderr, |s| s.purple()),
|
.if_supports_color(Stderr, |s| s.purple()),
|
||||||
);
|
);
|
||||||
with_project(directory, false, &mut action).unwrap_or(())
|
with_project(directory, seed, false, &mut action).unwrap_or(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,3 +38,4 @@ clap_complete = "4.3.2"
|
||||||
inquire = "0.6.2"
|
inquire = "0.6.2"
|
||||||
num-bigint = "0.4.3"
|
num-bigint = "0.4.3"
|
||||||
ordinal = "0.3.2"
|
ordinal = "0.3.2"
|
||||||
|
rand = "0.8.5"
|
||||||
|
|
|
@ -39,7 +39,7 @@ pub fn exec(
|
||||||
mainnet,
|
mainnet,
|
||||||
}: Args,
|
}: Args,
|
||||||
) -> miette::Result<()> {
|
) -> miette::Result<()> {
|
||||||
with_project(directory.as_deref(), false, |p| {
|
with_project(directory.as_deref(), u32::default(), false, |p| {
|
||||||
if rebuild {
|
if rebuild {
|
||||||
p.build(false, Tracing::silent())?;
|
p.build(false, Tracing::silent())?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,8 @@ use num_bigint::BigInt;
|
||||||
use ordinal::Ordinal;
|
use ordinal::Ordinal;
|
||||||
use owo_colors::{OwoColorize, Stream::Stderr};
|
use owo_colors::{OwoColorize, Stream::Stderr};
|
||||||
use pallas::ledger::primitives::alonzo::PlutusData;
|
use pallas::ledger::primitives::alonzo::PlutusData;
|
||||||
use std::str::FromStr;
|
use std::{fs, path::PathBuf, process, str::FromStr};
|
||||||
use std::{fs, path::PathBuf, process, rc::Rc};
|
use uplc::ast::Data as UplcData;
|
||||||
use uplc::ast::{Constant, Data as UplcData, DeBruijn, Term};
|
|
||||||
|
|
||||||
/// Apply a parameter to a parameterized validator.
|
/// Apply a parameter to a parameterized validator.
|
||||||
#[derive(clap::Args)]
|
#[derive(clap::Args)]
|
||||||
|
@ -48,7 +47,7 @@ pub fn exec(
|
||||||
validator,
|
validator,
|
||||||
}: Args,
|
}: Args,
|
||||||
) -> miette::Result<()> {
|
) -> miette::Result<()> {
|
||||||
with_project(None, false, |p| {
|
with_project(None, u32::default(), false, |p| {
|
||||||
let title = module.as_ref().map(|m| {
|
let title = module.as_ref().map(|m| {
|
||||||
format!(
|
format!(
|
||||||
"{m}{}",
|
"{m}{}",
|
||||||
|
@ -68,7 +67,7 @@ pub fn exec(
|
||||||
.if_supports_color(Stderr, |s| s.bold()),
|
.if_supports_color(Stderr, |s| s.bold()),
|
||||||
);
|
);
|
||||||
|
|
||||||
let term: Term<DeBruijn> = match ¶meter {
|
let data: PlutusData = match ¶meter {
|
||||||
Some(param) => {
|
Some(param) => {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{} inputs",
|
"{} inputs",
|
||||||
|
@ -90,7 +89,7 @@ pub fn exec(
|
||||||
process::exit(1)
|
process::exit(1)
|
||||||
});
|
});
|
||||||
|
|
||||||
let data = uplc::plutus_data(&bytes)
|
uplc::plutus_data(&bytes)
|
||||||
.map_err::<Error, _>(|e| {
|
.map_err::<Error, _>(|e| {
|
||||||
blueprint::error::Error::MalformedParameter {
|
blueprint::error::Error::MalformedParameter {
|
||||||
hint: format!("Invalid Plutus data; malformed CBOR encoding: {e}"),
|
hint: format!("Invalid Plutus data; malformed CBOR encoding: {e}"),
|
||||||
|
@ -101,9 +100,7 @@ pub fn exec(
|
||||||
println!();
|
println!();
|
||||||
e.report();
|
e.report();
|
||||||
process::exit(1)
|
process::exit(1)
|
||||||
});
|
})
|
||||||
|
|
||||||
Term::Constant(Rc::new(Constant::Data(data)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
None => p.construct_parameter_incrementally(title, ask_schema)?,
|
None => p.construct_parameter_incrementally(title, ask_schema)?,
|
||||||
|
@ -114,16 +111,13 @@ pub fn exec(
|
||||||
" Applying"
|
" Applying"
|
||||||
.if_supports_color(Stderr, |s| s.purple())
|
.if_supports_color(Stderr, |s| s.purple())
|
||||||
.if_supports_color(Stderr, |s| s.bold()),
|
.if_supports_color(Stderr, |s| s.bold()),
|
||||||
match TryInto::<PlutusData>::try_into(term.clone()) {
|
{
|
||||||
Ok(data) => {
|
let padding = "\n ";
|
||||||
let padding = "\n ";
|
multiline(48, UplcData::to_hex(data.clone())).join(padding)
|
||||||
multiline(48, UplcData::to_hex(data)).join(padding)
|
|
||||||
}
|
|
||||||
Err(_) => term.to_pretty(),
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let blueprint = p.apply_parameter(title, &term)?;
|
let blueprint = p.apply_parameter(title, &data)?;
|
||||||
|
|
||||||
let json = serde_json::to_string_pretty(&blueprint).unwrap();
|
let json = serde_json::to_string_pretty(&blueprint).unwrap();
|
||||||
|
|
||||||
|
@ -179,16 +173,21 @@ fn ask_schema(
|
||||||
}
|
}
|
||||||
|
|
||||||
Schema::Data(Data::List(Items::Many(ref decls))) => {
|
Schema::Data(Data::List(Items::Many(ref decls))) => {
|
||||||
eprintln!(" {}", asking(schema, "Found", &format!("a {}-tuple", decls.len())));
|
eprintln!(
|
||||||
|
" {}",
|
||||||
|
asking(schema, "Found", &format!("a {}-tuple", decls.len()))
|
||||||
|
);
|
||||||
|
|
||||||
let mut elems = vec![];
|
let mut elems = vec![];
|
||||||
|
|
||||||
for (ix, decl) in decls.iter().enumerate() {
|
for (ix, decl) in decls.iter().enumerate() {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
" {} Tuple's {}{} element",
|
" {} Tuple's {}{} element",
|
||||||
"Asking".if_supports_color(Stderr, |s| s.purple()).if_supports_color(Stderr, |s| s.bold()),
|
"Asking"
|
||||||
ix+1,
|
.if_supports_color(Stderr, |s| s.purple())
|
||||||
Ordinal::<usize>(ix+1).suffix()
|
.if_supports_color(Stderr, |s| s.bold()),
|
||||||
|
ix + 1,
|
||||||
|
Ordinal::<usize>(ix + 1).suffix()
|
||||||
);
|
);
|
||||||
let inner_schema = lookup_declaration(&decl.clone().into(), definitions);
|
let inner_schema = lookup_declaration(&decl.clone().into(), definitions);
|
||||||
elems.push(ask_schema(&inner_schema, definitions)?);
|
elems.push(ask_schema(&inner_schema, definitions)?);
|
||||||
|
@ -252,7 +251,9 @@ fn ask_schema(
|
||||||
Ok(UplcData::constr(ix.try_into().unwrap(), fields))
|
Ok(UplcData::constr(ix.try_into().unwrap(), fields))
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => unimplemented!("Hey! You've found a case that we haven't implemented yet. Yes, we've been a bit lazy on that one... If that use-case is important to you, please let us know on Discord or on Github."),
|
_ => unimplemented!(
|
||||||
|
"Hey! You've found a case that we haven't implemented yet. Yes, we've been a bit lazy on that one... If that use-case is important to you, please let us know on Discord or on Github."
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ pub fn exec(
|
||||||
rebuild,
|
rebuild,
|
||||||
}: Args,
|
}: Args,
|
||||||
) -> miette::Result<()> {
|
) -> miette::Result<()> {
|
||||||
with_project(directory.as_deref(), false, |p| {
|
with_project(directory.as_deref(), u32::default(), false, |p| {
|
||||||
if rebuild {
|
if rebuild {
|
||||||
p.build(false, Tracing::silent())?;
|
p.build(false, Tracing::silent())?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ pub fn exec(
|
||||||
rebuild,
|
rebuild,
|
||||||
}: Args,
|
}: Args,
|
||||||
) -> miette::Result<()> {
|
) -> miette::Result<()> {
|
||||||
with_project(directory.as_deref(), false, |p| {
|
with_project(directory.as_deref(), u32::default(), false, |p| {
|
||||||
if rebuild {
|
if rebuild {
|
||||||
p.build(false, Tracing::silent())?;
|
p.build(false, Tracing::silent())?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use aiken_lang::ast::{TraceLevel, Tracing};
|
use aiken_lang::ast::{TraceLevel, Tracing};
|
||||||
use aiken_project::watch::{self, watch_project, with_project};
|
use aiken_project::watch::{self, watch_project, with_project};
|
||||||
use clap::builder::MapValueParser;
|
use clap::builder::{MapValueParser, PossibleValuesParser, TypedValueParser};
|
||||||
use clap::builder::{PossibleValuesParser, TypedValueParser};
|
|
||||||
use std::{path::PathBuf, process};
|
use std::{path::PathBuf, process};
|
||||||
|
|
||||||
#[derive(clap::Args)]
|
#[derive(clap::Args)]
|
||||||
|
@ -52,17 +51,23 @@ pub fn exec(
|
||||||
}: Args,
|
}: Args,
|
||||||
) -> miette::Result<()> {
|
) -> miette::Result<()> {
|
||||||
let result = if watch {
|
let result = if watch {
|
||||||
watch_project(directory.as_deref(), watch::default_filter, 500, |p| {
|
watch_project(
|
||||||
p.build(
|
directory.as_deref(),
|
||||||
uplc,
|
watch::default_filter,
|
||||||
match filter_traces {
|
u32::default(),
|
||||||
Some(filter_traces) => filter_traces(trace_level),
|
500,
|
||||||
None => Tracing::All(trace_level),
|
|p| {
|
||||||
},
|
p.build(
|
||||||
)
|
uplc,
|
||||||
})
|
match filter_traces {
|
||||||
|
Some(filter_traces) => filter_traces(trace_level),
|
||||||
|
None => Tracing::All(trace_level),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
with_project(directory.as_deref(), deny, |p| {
|
with_project(directory.as_deref(), u32::default(), deny, |p| {
|
||||||
p.build(
|
p.build(
|
||||||
uplc,
|
uplc,
|
||||||
match filter_traces {
|
match filter_traces {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use super::build::{filter_traces_parser, trace_level_parser};
|
use super::build::{filter_traces_parser, trace_level_parser};
|
||||||
use aiken_lang::ast::{TraceLevel, Tracing};
|
use aiken_lang::ast::{TraceLevel, Tracing};
|
||||||
use aiken_project::watch::{self, watch_project, with_project};
|
use aiken_project::watch::{self, watch_project, with_project};
|
||||||
|
use rand::prelude::*;
|
||||||
use std::{path::PathBuf, process};
|
use std::{path::PathBuf, process};
|
||||||
|
|
||||||
#[derive(clap::Args)]
|
#[derive(clap::Args)]
|
||||||
|
@ -25,6 +26,10 @@ pub struct Args {
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
watch: bool,
|
watch: bool,
|
||||||
|
|
||||||
|
/// An initial seed to initialize the pseudo-random generator for property-tests.
|
||||||
|
#[clap(long)]
|
||||||
|
seed: Option<u32>,
|
||||||
|
|
||||||
/// Only run tests if they match any of these strings.
|
/// Only run tests if they match any of these strings.
|
||||||
/// You can match a module with `-m aiken/list` or `-m list`.
|
/// You can match a module with `-m aiken/list` or `-m list`.
|
||||||
/// You can match a test with `-m "aiken/list.{map}"` or `-m "aiken/option.{flatten_1}"`
|
/// You can match a test with `-m "aiken/list.{map}"` or `-m "aiken/option.{flatten_1}"`
|
||||||
|
@ -66,28 +71,41 @@ pub fn exec(
|
||||||
watch,
|
watch,
|
||||||
filter_traces,
|
filter_traces,
|
||||||
trace_level,
|
trace_level,
|
||||||
|
seed,
|
||||||
}: Args,
|
}: Args,
|
||||||
) -> miette::Result<()> {
|
) -> miette::Result<()> {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
let seed = seed.unwrap_or_else(|| rng.gen());
|
||||||
|
|
||||||
let result = if watch {
|
let result = if watch {
|
||||||
watch_project(directory.as_deref(), watch::default_filter, 500, |p| {
|
watch_project(
|
||||||
p.check(
|
directory.as_deref(),
|
||||||
skip_tests,
|
watch::default_filter,
|
||||||
match_tests.clone(),
|
seed,
|
||||||
debug,
|
500,
|
||||||
exact_match,
|
|p| {
|
||||||
match filter_traces {
|
p.check(
|
||||||
Some(filter_traces) => filter_traces(trace_level),
|
skip_tests,
|
||||||
None => Tracing::All(trace_level),
|
match_tests.clone(),
|
||||||
},
|
debug,
|
||||||
)
|
exact_match,
|
||||||
})
|
seed,
|
||||||
|
match filter_traces {
|
||||||
|
Some(filter_traces) => filter_traces(trace_level),
|
||||||
|
None => Tracing::All(trace_level),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
with_project(directory.as_deref(), deny, |p| {
|
with_project(directory.as_deref(), seed, deny, |p| {
|
||||||
p.check(
|
p.check(
|
||||||
skip_tests,
|
skip_tests,
|
||||||
match_tests.clone(),
|
match_tests.clone(),
|
||||||
debug,
|
debug,
|
||||||
exact_match,
|
exact_match,
|
||||||
|
seed,
|
||||||
match filter_traces {
|
match filter_traces {
|
||||||
Some(filter_traces) => filter_traces(trace_level),
|
Some(filter_traces) => filter_traces(trace_level),
|
||||||
None => Tracing::All(trace_level),
|
None => Tracing::All(trace_level),
|
||||||
|
|
|
@ -29,11 +29,17 @@ pub fn exec(
|
||||||
}: Args,
|
}: Args,
|
||||||
) -> miette::Result<()> {
|
) -> miette::Result<()> {
|
||||||
let result = if watch {
|
let result = if watch {
|
||||||
watch_project(directory.as_deref(), watch::default_filter, 500, |p| {
|
watch_project(
|
||||||
|
directory.as_deref(),
|
||||||
|
watch::default_filter,
|
||||||
|
u32::default(),
|
||||||
|
500,
|
||||||
|
|p| p.docs(destination.clone()),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
with_project(directory.as_deref(), u32::default(), deny, |p| {
|
||||||
p.docs(destination.clone())
|
p.docs(destination.clone())
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
with_project(directory.as_deref(), deny, |p| p.docs(destination.clone()))
|
|
||||||
};
|
};
|
||||||
|
|
||||||
result.map_err(|_| process::exit(1))
|
result.map_err(|_| process::exit(1))
|
||||||
|
|
|
@ -30,39 +30,41 @@ pub fn exec(
|
||||||
cbor,
|
cbor,
|
||||||
}: Args,
|
}: Args,
|
||||||
) -> miette::Result<()> {
|
) -> miette::Result<()> {
|
||||||
let mut program = if cbor {
|
let mut program: Program<Name> = if cbor {
|
||||||
let cbor_hex = std::fs::read_to_string(&script).into_diagnostic()?;
|
let cbor_hex = std::fs::read_to_string(&script).into_diagnostic()?;
|
||||||
|
|
||||||
let raw_cbor = hex::decode(cbor_hex.trim()).into_diagnostic()?;
|
let raw_cbor = hex::decode(cbor_hex.trim()).into_diagnostic()?;
|
||||||
|
|
||||||
let prog = Program::<FakeNamedDeBruijn>::from_cbor(&raw_cbor, &mut Vec::new())
|
let program = Program::<FakeNamedDeBruijn>::from_cbor(&raw_cbor, &mut Vec::new())
|
||||||
.into_diagnostic()?;
|
.into_diagnostic()?;
|
||||||
|
|
||||||
prog.into()
|
let program: Program<NamedDeBruijn> = program.into();
|
||||||
|
|
||||||
|
Program::<Name>::try_from(program).into_diagnostic()?
|
||||||
} else if flat {
|
} else if flat {
|
||||||
let bytes = std::fs::read(&script).into_diagnostic()?;
|
let bytes = std::fs::read(&script).into_diagnostic()?;
|
||||||
|
|
||||||
let prog = Program::<FakeNamedDeBruijn>::from_flat(&bytes).into_diagnostic()?;
|
let program = Program::<FakeNamedDeBruijn>::from_flat(&bytes).into_diagnostic()?;
|
||||||
|
|
||||||
prog.into()
|
let program: Program<NamedDeBruijn> = program.into();
|
||||||
|
|
||||||
|
Program::<Name>::try_from(program).into_diagnostic()?
|
||||||
} else {
|
} else {
|
||||||
let code = std::fs::read_to_string(&script).into_diagnostic()?;
|
let code = std::fs::read_to_string(&script).into_diagnostic()?;
|
||||||
|
|
||||||
let prog = parser::program(&code).into_diagnostic()?;
|
parser::program(&code).into_diagnostic()?
|
||||||
|
|
||||||
Program::<NamedDeBruijn>::try_from(prog).into_diagnostic()?
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for arg in args {
|
for arg in args {
|
||||||
let term = parser::term(&arg).into_diagnostic()?;
|
let term = parser::term(&arg).into_diagnostic()?;
|
||||||
|
|
||||||
let term = Term::<NamedDeBruijn>::try_from(term).into_diagnostic()?;
|
program = program.apply_term(&term)
|
||||||
|
|
||||||
program = program.apply_term(&term);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let budget = ExBudget::default();
|
let budget = ExBudget::default();
|
||||||
|
|
||||||
|
let program = Program::<NamedDeBruijn>::try_from(program).into_diagnostic()?;
|
||||||
|
|
||||||
let mut eval_result = program.eval(budget);
|
let mut eval_result = program.eval(budget);
|
||||||
|
|
||||||
let cost = eval_result.cost();
|
let cost = eval_result.cost();
|
||||||
|
|
|
@ -7,6 +7,7 @@ use crate::{
|
||||||
eval_result::EvalResult,
|
eval_result::EvalResult,
|
||||||
Machine,
|
Machine,
|
||||||
},
|
},
|
||||||
|
parser::interner::Interner,
|
||||||
};
|
};
|
||||||
use num_bigint::BigInt;
|
use num_bigint::BigInt;
|
||||||
use num_traits::ToPrimitive;
|
use num_traits::ToPrimitive;
|
||||||
|
@ -18,7 +19,6 @@ use pallas::ledger::{
|
||||||
},
|
},
|
||||||
traverse::ComputeHash,
|
traverse::ComputeHash,
|
||||||
};
|
};
|
||||||
|
|
||||||
use serde::{
|
use serde::{
|
||||||
self,
|
self,
|
||||||
de::{self, Deserialize, Deserializer, MapAccess, Visitor},
|
de::{self, Deserialize, Deserializer, MapAccess, Visitor},
|
||||||
|
@ -58,21 +58,8 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// We use this to apply the validator to Datum,
|
/// A convenient and faster version that `apply_term` since the program doesn't need to be
|
||||||
/// then redeemer, then ScriptContext. If datum is
|
/// re-interned (constant Data do not introduce new bindings).
|
||||||
/// even necessary (i.e. minting policy).
|
|
||||||
pub fn apply_term(&self, term: &Term<T>) -> Self {
|
|
||||||
let applied_term = Term::Apply {
|
|
||||||
function: Rc::new(self.term.clone()),
|
|
||||||
argument: Rc::new(term.clone()),
|
|
||||||
};
|
|
||||||
|
|
||||||
Program {
|
|
||||||
version: self.version,
|
|
||||||
term: applied_term,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply_data(&self, plutus_data: PlutusData) -> Self {
|
pub fn apply_data(&self, plutus_data: PlutusData) -> Self {
|
||||||
let applied_term = Term::Apply {
|
let applied_term = Term::Apply {
|
||||||
function: Rc::new(self.term.clone()),
|
function: Rc::new(self.term.clone()),
|
||||||
|
@ -86,6 +73,27 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Program<Name> {
|
||||||
|
/// We use this to apply the validator to Datum,
|
||||||
|
/// then redeemer, then ScriptContext. If datum is
|
||||||
|
/// even necessary (i.e. minting policy).
|
||||||
|
pub fn apply_term(&self, term: &Term<Name>) -> Self {
|
||||||
|
let applied_term = Term::Apply {
|
||||||
|
function: Rc::new(self.term.clone()),
|
||||||
|
argument: Rc::new(term.clone()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut program = Program {
|
||||||
|
version: self.version,
|
||||||
|
term: applied_term,
|
||||||
|
};
|
||||||
|
|
||||||
|
Interner::new().program(&mut program);
|
||||||
|
|
||||||
|
program
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, T> Display for Program<T>
|
impl<'a, T> Display for Program<T>
|
||||||
where
|
where
|
||||||
T: Binder<'a>,
|
T: Binder<'a>,
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::ast::{NamedDeBruijn, Term, Type};
|
||||||
|
|
||||||
use super::{ExBudget, Value};
|
use super::{ExBudget, Value};
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug, miette::Diagnostic)]
|
#[derive(Debug, Clone, thiserror::Error, miette::Diagnostic)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Over budget mem: {} & cpu: {}", .0.mem, .0.cpu)]
|
#[error("Over budget mem: {} & cpu: {}", .0.mem, .0.cpu)]
|
||||||
OutOfExError(ExBudget),
|
OutOfExError(ExBudget),
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::ast::{Constant, NamedDeBruijn, Term};
|
||||||
|
|
||||||
use super::{cost_model::ExBudget, Error};
|
use super::{cost_model::ExBudget, Error};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct EvalResult {
|
pub struct EvalResult {
|
||||||
result: Result<Term<NamedDeBruijn>, Error>,
|
result: Result<Term<NamedDeBruijn>, Error>,
|
||||||
remaining_budget: ExBudget,
|
remaining_budget: ExBudget,
|
||||||
|
@ -39,11 +40,11 @@ impl EvalResult {
|
||||||
} else {
|
} else {
|
||||||
self.result.is_err()
|
self.result.is_err()
|
||||||
|| matches!(self.result, Ok(Term::Error))
|
|| matches!(self.result, Ok(Term::Error))
|
||||||
|| matches!(self.result, Ok(Term::Constant(ref con)) if matches!(con.as_ref(), Constant::Bool(false)))
|
|| !matches!(self.result, Ok(Term::Constant(ref con)) if matches!(con.as_ref(), Constant::Bool(true)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn result(self) -> Result<Term<NamedDeBruijn>, Error> {
|
pub fn result(&self) -> Result<Term<NamedDeBruijn>, Error> {
|
||||||
self.result
|
self.result.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
use std::{collections::VecDeque, mem::size_of, ops::Deref, rc::Rc};
|
use std::{collections::VecDeque, mem::size_of, ops::Deref, rc::Rc};
|
||||||
|
|
||||||
use num_bigint::BigInt;
|
|
||||||
use num_traits::{Signed, ToPrimitive, Zero};
|
|
||||||
use pallas::ledger::primitives::babbage::{self, PlutusData};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{Constant, NamedDeBruijn, Term, Type},
|
ast::{Constant, NamedDeBruijn, Term, Type},
|
||||||
builtins::DefaultFunction,
|
builtins::DefaultFunction,
|
||||||
};
|
};
|
||||||
|
use num_bigint::BigInt;
|
||||||
|
use num_traits::{Signed, ToPrimitive, Zero};
|
||||||
|
use pallas::ledger::primitives::babbage::{self, PlutusData};
|
||||||
|
|
||||||
use super::{runtime::BuiltinRuntime, Error};
|
use super::{runtime::BuiltinRuntime, Error};
|
||||||
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
# This file was generated by Aiken
|
|
||||||
# You typically do not need to edit this file
|
|
||||||
|
|
||||||
requirements = []
|
|
||||||
packages = []
|
|
||||||
|
|
||||||
[etags]
|
|
|
@ -1,4 +0,0 @@
|
||||||
name = "thing/thing"
|
|
||||||
version = "0.0.0"
|
|
||||||
license = "Apache-2.0"
|
|
||||||
description = "Aiken contracts for project 'thing/thing'"
|
|
|
@ -1,10 +0,0 @@
|
||||||
pub fn wow(a: Void) -> Int {
|
|
||||||
when Some(a) is {
|
|
||||||
Some(Void) -> 42
|
|
||||||
None -> 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test wow_1() {
|
|
||||||
wow(Void) == 42
|
|
||||||
}
|
|
Loading…
Reference in New Issue