Merge branch 'aiken-watch'

This commit is contained in:
KtorZ 2023-11-25 15:14:18 +01:00
commit c2725abcea
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
63 changed files with 1391 additions and 993 deletions

View File

@ -1,6 +1,20 @@
# Changelog
## v1.0.20-alpha - 10/25/2023
## v1.0.21-alpha - unreleased
### Added
- **aiken**: `--watch` flag on the `build`, `check` and `docs` commands to automatically watch and re-execute the command on file changes.
### Changed
N/A
### Fixed
N/A
## v1.0.20-alpha - 2023-10-25
### Added

111
Cargo.lock generated vendored
View File

@ -141,6 +141,7 @@ dependencies = [
"itertools",
"miette",
"minicbor",
"notify",
"owo-colors",
"pallas",
"pallas-traverse",
@ -360,6 +361,12 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
[[package]]
name = "block-buffer"
version = "0.10.4"
@ -505,7 +512,7 @@ checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f"
dependencies = [
"anstream",
"anstyle",
"bitflags",
"bitflags 1.3.2",
"clap_lex",
"strsim",
"terminal_size 0.2.6",
@ -662,7 +669,7 @@ version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"crossterm_winapi",
"libc",
"mio",
@ -866,6 +873,18 @@ dependencies = [
"subtle",
]
[[package]]
name = "filetime"
version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.3.5",
"windows-sys 0.48.0",
]
[[package]]
name = "fixedbitset"
version = "0.4.2"
@ -921,6 +940,15 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "fsevent-sys"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
dependencies = [
"libc",
]
[[package]]
name = "fslock"
version = "0.2.1"
@ -1054,7 +1082,7 @@ version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b989d6a7ca95a362cf2cfc5ad688b3a467be1f87e480b8dad07fee8c79b0044"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"libc",
"libgit2-sys",
"log",
@ -1293,13 +1321,33 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f2cb48b81b1dc9f39676bf99f5499babfec7cd8fe14307f7b3d747208fb5690"
[[package]]
name = "inotify"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
dependencies = [
"bitflags 1.3.2",
"inotify-sys",
"libc",
]
[[package]]
name = "inotify-sys"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
dependencies = [
"libc",
]
[[package]]
name = "inquire"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c33e7c1ddeb15c9abcbfef6029d8e29f69b52b6d6c891031b88ed91b5065803b"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"crossterm",
"dyn-clone",
"lazy_static",
@ -1414,6 +1462,26 @@ dependencies = [
"signature",
]
[[package]]
name = "kqueue"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c"
dependencies = [
"kqueue-sys",
"libc",
]
[[package]]
name = "kqueue-sys"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
dependencies = [
"bitflags 1.3.2",
"libc",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -1505,7 +1573,7 @@ version = "0.94.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b63735a13a1f9cd4f4835223d828ed9c2e35c8c5e61837774399f558b6a1237"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"serde",
"serde_json",
"serde_repr",
@ -1660,6 +1728,25 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "notify"
version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
dependencies = [
"bitflags 2.4.1",
"crossbeam-channel",
"filetime",
"fsevent-sys",
"inotify",
"kqueue",
"libc",
"log",
"mio",
"walkdir",
"windows-sys 0.48.0",
]
[[package]]
name = "num-bigint"
version = "0.4.3"
@ -1728,7 +1815,7 @@ version = "0.10.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"cfg-if",
"foreign-types",
"libc",
@ -2069,7 +2156,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65"
dependencies = [
"bit-set",
"bitflags",
"bitflags 1.3.2",
"byteorder",
"lazy_static",
"num-traits",
@ -2097,7 +2184,7 @@ version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"memchr",
"unicase",
]
@ -2184,7 +2271,7 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
"bitflags 1.3.2",
]
[[package]]
@ -2193,7 +2280,7 @@ version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
dependencies = [
"bitflags",
"bitflags 1.3.2",
]
[[package]]
@ -2283,7 +2370,7 @@ version = "0.37.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"errno",
"io-lifetimes",
"libc",
@ -2377,7 +2464,7 @@ version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"core-foundation",
"core-foundation-sys",
"libc",

View File

@ -516,7 +516,7 @@ impl<'comments> Formatter<'comments> {
.group();
// Format body
let body = self.expr(body);
let body = self.expr(body, true);
// Add any trailing comments
let body = match printed_comments(self.pop_comments(end_location), false) {
@ -609,8 +609,10 @@ impl<'comments> Formatter<'comments> {
) -> Document<'a> {
let args = wrap_args(args.iter().map(|e| (self.fn_arg(e), false))).group();
let body = match body {
UntypedExpr::Trace { .. } | UntypedExpr::When { .. } => self.expr(body).force_break(),
_ => self.expr(body),
UntypedExpr::Trace { .. } | UntypedExpr::When { .. } => {
self.expr(body, true).force_break()
}
_ => self.expr(body, true),
};
let header = "fn".to_doc().append(args);
@ -634,15 +636,19 @@ impl<'comments> Formatter<'comments> {
fn sequence<'a>(&mut self, expressions: &'a [UntypedExpr]) -> Document<'a> {
let count = expressions.len();
let mut documents = Vec::with_capacity(count * 2);
for (i, expression) in expressions.iter().enumerate() {
let preceding_newline = self.pop_empty_lines(expression.start_byte_index());
if i != 0 && preceding_newline {
documents.push(lines(2));
} else if i != 0 {
documents.push(lines(1));
}
documents.push(self.expr(expression).group());
documents.push(self.expr(expression, false).group());
}
documents.to_doc().force_break()
}
@ -653,8 +659,6 @@ impl<'comments> Formatter<'comments> {
kind: AssignmentKind,
annotation: &'a Option<Annotation>,
) -> Document<'a> {
self.pop_empty_lines(pattern.location().end);
let keyword = match kind {
AssignmentKind::Let => "let",
AssignmentKind::Expect => "expect",
@ -667,6 +671,8 @@ impl<'comments> Formatter<'comments> {
keyword.to_doc().append(self.case_clause_value(value))
}
_ => {
self.pop_empty_lines(pattern.location().end);
let pattern = self.pattern(pattern);
let annotation = annotation
@ -782,7 +788,7 @@ impl<'comments> Formatter<'comments> {
}
}
pub fn expr<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> {
pub fn expr<'a>(&mut self, expr: &'a UntypedExpr, is_top_level: bool) -> Document<'a> {
let comments = self.pop_comments(expr.start_byte_index());
let document = match expr {
@ -821,7 +827,18 @@ impl<'comments> Formatter<'comments> {
UntypedExpr::String { value, .. } => self.string(value),
UntypedExpr::Sequence { expressions, .. } => self.sequence(expressions),
UntypedExpr::Sequence { expressions, .. } => {
let sequence = self.sequence(expressions);
if is_top_level {
sequence
} else {
"{".to_doc()
.append(line().append(sequence).nest(INDENT).group())
.append(line())
.append("}")
}
}
UntypedExpr::Var { name, .. } if name.contains(CAPTURE_VARIABLE) => "_".to_doc(),
@ -878,7 +895,10 @@ impl<'comments> Formatter<'comments> {
UntypedExpr::FieldAccess {
label, container, ..
} => self.expr(container).append(".").append(label.as_str()),
} => self
.expr(container, false)
.append(".")
.append(label.as_str()),
UntypedExpr::RecordUpdate {
constructor,
@ -893,7 +913,7 @@ impl<'comments> Formatter<'comments> {
UntypedExpr::TupleIndex { index, tuple, .. } => {
let suffix = Ordinal(*index + 1).suffix().to_doc();
self.expr(tuple)
self.expr(tuple, false)
.append(".".to_doc())
.append((index + 1).to_doc())
.append(suffix)
@ -953,7 +973,7 @@ impl<'comments> Formatter<'comments> {
} else {
line()
})
.append(self.expr(then)),
.append(self.expr(then, false)),
}
}
@ -1027,7 +1047,7 @@ impl<'comments> Formatter<'comments> {
false
};
self.expr(fun)
self.expr(fun, false)
.append(wrap_args(
args.iter()
.map(|a| (self.call_arg(a, needs_curly), needs_curly)),
@ -1051,7 +1071,7 @@ impl<'comments> Formatter<'comments> {
let else_begin = line().append("} else {");
let else_body = line().append(self.expr(final_else)).nest(INDENT);
let else_body = line().append(self.expr(final_else, true)).nest(INDENT);
let else_end = line().append("}");
@ -1072,7 +1092,7 @@ impl<'comments> Formatter<'comments> {
.append(break_("{", " {"))
.group();
let if_body = line().append(self.expr(&branch.body)).nest(INDENT);
let if_body = line().append(self.expr(&branch.body, true)).nest(INDENT);
if_begin.append(if_body)
}
@ -1110,8 +1130,8 @@ impl<'comments> Formatter<'comments> {
args: &'a [UntypedRecordUpdateArg],
) -> Document<'a> {
use std::iter::once;
let constructor_doc = self.expr(constructor);
let spread_doc = "..".to_doc().append(self.expr(&spread.base));
let constructor_doc = self.expr(constructor, false);
let spread_doc = "..".to_doc().append(self.expr(&spread.base, false));
let arg_docs = args.iter().map(|a| (self.record_update_arg(a), true));
let all_arg_docs = once((spread_doc, true)).chain(arg_docs);
constructor_doc.append(wrap_args(all_arg_docs)).group()
@ -1128,14 +1148,14 @@ impl<'comments> Formatter<'comments> {
let left_precedence = left.binop_precedence();
let right_precedence = right.binop_precedence();
let left = self.expr(left);
let right = self.expr(right);
let left = self.expr(left, false);
let right = self.expr(right, false);
self.operator_side(left, precedence, left_precedence)
.append(" ")
.append(name)
.append(" ")
.append(self.operator_side(right, precedence, right_precedence - 1))
.append(self.operator_side(right, precedence, right_precedence.saturating_sub(1)))
}
pub fn operator_side<'a>(&mut self, doc: Document<'a>, op: u8, side: u8) -> Document<'a> {
@ -1161,7 +1181,9 @@ impl<'comments> Formatter<'comments> {
.append(
line()
.append(join(
expressions.iter().map(|expression| self.expr(expression)),
expressions
.iter()
.map(|expression| self.expr(expression, false)),
",".to_doc().append(line()),
))
.nest(INDENT)
@ -1241,10 +1263,10 @@ impl<'comments> Formatter<'comments> {
if hole_in_first_position && args.len() == 1 {
// x |> fun(_)
self.expr(fun)
self.expr(fun, false)
} else if hole_in_first_position {
// x |> fun(_, 2, 3)
self.expr(fun).append(
self.expr(fun, false).append(
wrap_args(
args.iter()
.skip(1)
@ -1254,7 +1276,7 @@ impl<'comments> Formatter<'comments> {
)
} else {
// x |> fun(1, _, 3)
self.expr(fun)
self.expr(fun, false)
.append(wrap_args(args.iter().map(|a| (self.call_arg(a, false), false))).group())
}
}
@ -1267,14 +1289,14 @@ impl<'comments> Formatter<'comments> {
..
} => match args.as_slice() {
[first, second] if is_breakable_expr(&second.value) && first.is_capture_hole() => {
self.expr(fun)
self.expr(fun, false)
.append("(_, ")
.append(self.call_arg(second, false))
.append(")")
.group()
}
_ => self.expr(fun).append(
_ => self.expr(fun, false).append(
wrap_args(args.iter().map(|a| (self.call_arg(a, false), false))).group(),
),
},
@ -1556,12 +1578,12 @@ impl<'comments> Formatter<'comments> {
| UntypedExpr::Sequence { .. }
| UntypedExpr::Assignment { .. } => "{"
.to_doc()
.append(line().append(self.expr(expr)).nest(INDENT))
.append(line().append(self.expr(expr, true)).nest(INDENT))
.append(line())
.append("}")
.force_break(),
_ => self.expr(expr),
_ => self.expr(expr, false),
}
}
@ -1598,18 +1620,21 @@ impl<'comments> Formatter<'comments> {
| UntypedExpr::Sequence { .. }
| UntypedExpr::Assignment { .. } => " {"
.to_doc()
.append(line().append(self.expr(expr)).nest(INDENT).group())
.append(line().append(self.expr(expr, true)).nest(INDENT).group())
.append(line())
.append("}")
.force_break(),
UntypedExpr::Fn { .. } | UntypedExpr::List { .. } => {
line().append(self.expr(expr)).nest(INDENT).group()
line().append(self.expr(expr, false)).nest(INDENT).group()
}
UntypedExpr::When { .. } => line().append(self.expr(expr)).nest(INDENT).group(),
UntypedExpr::When { .. } => line().append(self.expr(expr, false)).nest(INDENT).group(),
_ => break_("", " ").append(self.expr(expr)).nest(INDENT).group(),
_ => break_("", " ")
.append(self.expr(expr, false))
.nest(INDENT)
.group(),
}
}
@ -1647,7 +1672,7 @@ impl<'comments> Formatter<'comments> {
|| break_(",", ", ")
};
let elements_document = join(elements.iter().map(|e| self.wrap_expr(e)), comma());
let tail = tail.map(|e| self.expr(e));
let tail = tail.map(|e| self.expr(e, false));
list(elements_document, elements.len(), tail)
}
@ -1720,7 +1745,7 @@ impl<'comments> Formatter<'comments> {
let right = self.clause_guard(right);
self.operator_side(left, name_precedence, left_precedence)
.append(name)
.append(self.operator_side(right, name_precedence, right_precedence - 1))
.append(self.operator_side(right, name_precedence, right_precedence.saturating_sub(1)))
}
fn clause_guard<'a>(&mut self, clause_guard: &'a UntypedClauseGuard) -> Document<'a> {
@ -1771,7 +1796,7 @@ impl<'comments> Formatter<'comments> {
fn wrap_unary_op<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> {
match expr {
UntypedExpr::BinOp { .. } => "(".to_doc().append(self.expr(expr)).append(")"),
UntypedExpr::BinOp { .. } => "(".to_doc().append(self.expr(expr, false)).append(")"),
_ => self.wrap_expr(expr),
}
}

View File

@ -4049,6 +4049,12 @@ impl<'a> CodeGenerator<'a> {
Term::equals_string()
} else if tipo.is_bytearray() {
Term::equals_bytestring()
} else if tipo.is_bls381_12_g1() {
Term::bls12_381_g1_equal()
} else if tipo.is_bls381_12_g2() {
Term::bls12_381_g2_equal()
} else if tipo.is_ml_result() {
panic!("ML Result equality is not supported")
} else {
Term::equals_data()
};

View File

@ -579,6 +579,12 @@ pub fn get_variant_name(t: &Rc<Type>) -> String {
"_bool".to_string()
} else if t.is_bytearray() {
"_bytearray".to_string()
} else if t.is_bls381_12_g1() {
"_bls381_12_g1".to_string()
} else if t.is_bls381_12_g2() {
"_bls381_12_g2".to_string()
} else if t.is_ml_result() {
"_ml_result".to_string()
} else if t.is_map() {
let mut full_type = vec!["_map".to_string()];
let pair_type = &t.get_inner_types()[0];
@ -1305,7 +1311,7 @@ pub fn convert_constants_to_data(constants: Vec<Rc<UplcConstant>>) -> Vec<UplcCo
UplcConstant::Bls12_381G2Element(b) => UplcConstant::Data(PlutusData::BoundedBytes(
b.deref().clone().compress().into(),
)),
UplcConstant::Bls12_381MlResult(_) => unreachable!("Bls12_381MlResult not supported"),
UplcConstant::Bls12_381MlResult(_) => panic!("Bls12_381MlResult not supported"),
};
new_constants.push(constant);
}

View File

@ -66,6 +66,25 @@ fn validator_illegal_return_type() {
))
}
#[test]
fn validator_useless_pub() {
let source_code = r#"
type Datum {
thing: Int
}
validator {
pub fn foo(_d: Datum, _r, _c) {
True
}
}
"#;
let (warnings, _) = check_validator(parse(source_code)).unwrap();
assert!(matches!(warnings[0], Warning::PubInValidatorModule { .. }))
}
#[test]
fn validator_illegal_arity() {
let source_code = r#"

View File

@ -62,6 +62,77 @@ fn format_if() {
);
}
#[test]
fn format_logic_op_with_code_block() {
assert_format!(
r#"
fn foo() {
True || {
let bar = 1
bar == bar
}
}
"#
);
}
#[test]
fn format_grouped_expression() {
assert_format!(
r#"
fn foo() {
y == { x |> f }
}
"#
);
}
#[test]
fn format_grouped_expression_2() {
assert_format!(
r#"
fn foo() {
( y == x ) |> f
}
"#
);
}
#[test]
fn format_grouped_expression_3() {
assert_format!(
r#"
fn foo() {
{ x |> f } == y
}
"#
);
}
#[test]
fn format_grouped_expression_4() {
assert_format!(
r#"
fn foo() {
x |> { f == y }
}
"#
);
}
#[test]
fn format_preserve_newline_after_bool_expect() {
assert_format!(
r#"
fn foo() {
expect 1 == 1
False
}
"#
);
}
#[test]
fn format_validator() {
assert_format!(

View File

@ -0,0 +1,8 @@
---
source: crates/aiken-lang/src/tests/format.rs
description: "Code:\n\nfn foo() {\n y == { x |> f }\n}\n"
---
fn foo() {
y == ( x |> f )
}

View File

@ -0,0 +1,8 @@
---
source: crates/aiken-lang/src/tests/format.rs
description: "Code:\n\nfn foo() {\n ( y == x ) |> f\n}\n"
---
fn foo() {
( y == x ) |> f
}

View File

@ -0,0 +1,8 @@
---
source: crates/aiken-lang/src/tests/format.rs
description: "Code:\n\nfn foo() {\n { x |> f } == y\n}\n"
---
fn foo() {
( x |> f ) == y
}

View File

@ -0,0 +1,8 @@
---
source: crates/aiken-lang/src/tests/format.rs
description: "Code:\n\nfn foo() {\n x |> { f == y }\n}\n"
---
fn foo() {
x |> f == y
}

View File

@ -0,0 +1,11 @@
---
source: crates/aiken-lang/src/tests/format.rs
description: "Code:\n\nfn foo() {\n True || {\n let bar = 1\n bar == bar\n }\n}\n"
---
fn foo() {
True || {
let bar = 1
bar == bar
}
}

View File

@ -0,0 +1,10 @@
---
source: crates/aiken-lang/src/tests/format.rs
description: "Code:\n\nfn foo() {\n expect 1 == 1\n\n False\n}\n"
---
fn foo() {
expect 1 == 1
False
}

View File

@ -1131,15 +1131,15 @@ fn suggest_constructor_pattern(
}
fn suggest_unify(
expected: &Rc<Type>,
given: &Rc<Type>,
expected: &Type,
given: &Type,
situation: &Option<UnifyErrorSituation>,
rigid_type_names: &HashMap<u64, String>,
) -> String {
let expected_str = expected.to_pretty_with_names(rigid_type_names.clone(), 0);
let given_str = given.to_pretty_with_names(rigid_type_names.clone(), 0);
let (expected, given) = match (expected.as_ref(), given.as_ref()) {
let (expected, given) = match (expected, given) {
(
Type::App {
module: expected_module,
@ -1630,7 +1630,7 @@ pub enum UnknownRecordFieldSituation {
fn format_suggestion(sample: &UntypedExpr) -> String {
Formatter::new()
.expr(sample)
.expr(sample, false)
.to_pretty_string(70)
.lines()
.enumerate()

View File

@ -426,7 +426,9 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
tipo: string(),
value: format!(
"{} ? False",
format::Formatter::new().expr(&value).to_pretty_string(999)
format::Formatter::new()
.expr(&value, false)
.to_pretty_string(999)
),
};

View File

@ -150,9 +150,9 @@ impl FieldMap {
self.fields
.keys()
.cloned()
.filter(|f| !given.contains(f))
.sorted()
.cloned()
.collect()
}
}

View File

@ -26,6 +26,7 @@ indexmap = "1.9.2"
itertools = "0.10.5"
miette = { version = "5.9.0", features = ["fancy"] }
minicbor = "0.19.1"
notify = "6.1.1"
owo-colors = { version = "3.5.0", features = ["supports-colors"] }
pallas = "0.18.0"
pallas-traverse = "0.18.0"

View File

@ -1,9 +1,6 @@
use crate::{
blueprint::error as blueprint, deps::manifest::Package, package_name::PackageName, pretty,
script::EvalHint,
};
use crate::{blueprint::error as blueprint, deps::manifest::Package, package_name::PackageName};
use aiken_lang::{
ast::{self, BinOp, Span},
ast::{self, Span},
error::ExtraData,
parser::error::ParseError,
tipo,
@ -18,7 +15,6 @@ use std::{
ops::Deref,
path::{Path, PathBuf},
};
use uplc::machine::cost_model::ExBudget;
use zip::result::ZipError;
#[allow(dead_code)]
@ -97,7 +93,7 @@ pub enum Error {
path: PathBuf,
verbose: bool,
src: String,
evaluation_hint: Option<EvalHint>,
evaluation_hint: Option<String>,
},
#[error(
@ -266,31 +262,45 @@ impl Diagnostic for Error {
}
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
fn boxed<'a>(s: Box<dyn Display + 'a>) -> Box<dyn Display + 'a> {
Box::new(format!(
" {} {}",
"Error"
.if_supports_color(Stdout, |s| s.red())
.if_supports_color(Stdout, |s| s.bold()),
format!("{s}").if_supports_color(Stdout, |s| s.red())
))
}
match self {
Error::DuplicateModule { .. } => Some(Box::new("aiken::module::duplicate")),
Error::DuplicateModule { .. } => Some(boxed(Box::new("aiken::module::duplicate"))),
Error::FileIo { .. } => None,
Error::Blueprint(e) => e.code(),
Error::ImportCycle { .. } => Some(Box::new("aiken::module::cyclical")),
Error::Parse { .. } => Some(Box::new("aiken::parser")),
Error::Type { error, .. } => Some(Box::new(format!(
Error::Blueprint(e) => e.code().map(boxed),
Error::ImportCycle { .. } => Some(boxed(Box::new("aiken::module::cyclical"))),
Error::Parse { .. } => Some(boxed(Box::new("aiken::parser"))),
Error::Type { error, .. } => Some(boxed(Box::new(format!(
"aiken::check{}",
error.code().map(|s| format!("::{s}")).unwrap_or_default()
))),
)))),
Error::StandardIo(_) => None,
Error::MissingManifest { .. } => None,
Error::TomlLoading { .. } => Some(Box::new("aiken::loading::toml")),
Error::TomlLoading { .. } => Some(boxed(Box::new("aiken::loading::toml"))),
Error::Format { .. } => None,
Error::TestFailure { path, .. } => Some(Box::new(path.to_str().unwrap_or(""))),
Error::TestFailure { path, .. } => Some(boxed(Box::new(path.to_str().unwrap_or("")))),
Error::Http(_) => Some(Box::new("aiken::packages::download")),
Error::ZipExtract(_) => None,
Error::JoinError(_) => None,
Error::UnknownPackageVersion { .. } => Some(Box::new("aiken::packages::resolve")),
Error::UnableToResolvePackage { .. } => Some(Box::new("aiken::package::download")),
Error::UnknownPackageVersion { .. } => {
Some(boxed(Box::new("aiken::packages::resolve")))
}
Error::UnableToResolvePackage { .. } => {
Some(boxed(Box::new("aiken::package::download")))
}
Error::Json { .. } => None,
Error::MalformedStakeAddress { .. } => None,
Error::NoValidatorNotFound { .. } => None,
Error::MoreThanOneValidatorFound { .. } => None,
Error::Module(e) => e.code(),
Error::Module(e) => e.code().map(boxed),
}
}
@ -313,33 +323,9 @@ impl Diagnostic for Error {
Error::MissingManifest { .. } => Some(Box::new("Try running `aiken new <REPOSITORY/PROJECT>` to initialise a project with an example manifest.")),
Error::TomlLoading { .. } => None,
Error::Format { .. } => None,
Error::TestFailure { evaluation_hint, .. } =>{
match evaluation_hint {
None => None,
Some(hint) => {
let budget = ExBudget { mem: i64::MAX, cpu: i64::MAX, };
let left = pretty::boxed("left", &match hint.left.clone().eval(budget).result() {
Ok(term) => format!("{term}"),
Err(err) => format!("{err}"),
});
let right = pretty::boxed("right", &match hint.right.clone().eval(budget).result() {
Ok(term) => format!("{term}"),
Err(err) => format!("{err}"),
});
let msg = match hint.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
}?;
Some(Box::new(msg))
}
}
Error::TestFailure { evaluation_hint, .. } => match evaluation_hint {
None => None,
Some(hint) => Some(Box::new(hint.to_string()))
},
Error::Http(_) => None,
Error::ZipExtract(_) => None,
@ -560,14 +546,24 @@ impl Diagnostic for Warning {
}
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
fn boxed<'a>(s: Box<dyn Display + 'a>) -> Box<dyn Display + 'a> {
Box::new(format!(
" {} {}",
"Warning"
.if_supports_color(Stdout, |s| s.yellow())
.if_supports_color(Stdout, |s| s.bold()),
format!("{s}").if_supports_color(Stdout, |s| s.yellow())
))
}
match self {
Warning::Type { warning, .. } => Some(Box::new(format!(
Warning::Type { warning, .. } => Some(boxed(Box::new(format!(
"aiken::check{}",
warning.code().map(|s| format!("::{s}")).unwrap_or_default()
))),
Warning::NoValidators => Some(Box::new("aiken::check")),
)))),
Warning::NoValidators => Some(boxed(Box::new("aiken::check"))),
Warning::DependencyAlreadyExists { .. } => {
Some(Box::new("aiken::packages::already_exists"))
Some(boxed(Box::new("aiken::packages::already_exists")))
}
}
}

View File

@ -14,6 +14,7 @@ pub mod script;
pub mod telemetry;
#[cfg(test)]
mod tests;
pub mod watch;
use crate::blueprint::{
definitions::Definitions,
@ -328,7 +329,11 @@ where
Some(Error::TestFailure {
name: e.script.name.clone(),
path: e.script.input_path.clone(),
evaluation_hint: e.script.evaluation_hint.clone(),
evaluation_hint: e
.script
.evaluation_hint
.as_ref()
.map(|hint| hint.to_string()),
src: e.script.program.to_pretty(),
verbose,
})

View File

@ -1,6 +1,9 @@
use crate::{ExBudget, Term};
use crate::{pretty, ExBudget, Term};
use aiken_lang::ast::BinOp;
use std::path::PathBuf;
use std::{
fmt::{self, Display},
path::PathBuf,
};
use uplc::ast::{NamedDeBruijn, Program};
#[derive(Debug, Clone)]
@ -42,6 +45,48 @@ pub struct EvalHint {
pub right: Program<NamedDeBruijn>,
}
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 = 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,

View File

@ -1,5 +1,11 @@
use crate::pretty;
use crate::script::EvalInfo;
use std::{fmt::Display, path::PathBuf};
use owo_colors::{
OwoColorize,
Stream::{self, Stderr},
};
use std::{collections::BTreeMap, fmt::Display, path::PathBuf};
use uplc::machine::cost_model::ExBudget;
pub trait EventListener {
fn handle_event(&self, _event: Event) {}
@ -64,3 +70,339 @@ impl Display for DownloadSource {
}
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct Terminal;
impl EventListener for Terminal {
fn handle_event(&self, event: Event) {
match event {
Event::StartingCompilation {
name,
version,
root,
} => {
eprintln!(
"{} {} {} ({})",
" Compiling"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
name.if_supports_color(Stderr, |s| s.bold()),
version,
root.display()
.if_supports_color(Stderr, |s| s.bright_blue())
);
}
Event::BuildingDocumentation {
name,
version,
root,
} => {
eprintln!(
"{} {} {} ({})",
" Generating documentation"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
name.if_supports_color(Stderr, |s| s.bold()),
version,
root.to_str()
.unwrap_or("")
.if_supports_color(Stderr, |s| s.bright_blue())
);
}
Event::WaitingForBuildDirLock => {
eprintln!(
"{}",
"Waiting for build directory lock ..."
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple())
);
}
Event::DumpingUPLC { path } => {
eprintln!(
"{} {} ({})",
" Exporting"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
"UPLC".if_supports_color(Stderr, |s| s.bold()),
path.display()
.if_supports_color(Stderr, |s| s.bright_blue())
);
}
Event::GeneratingBlueprint { path } => {
eprintln!(
"{} {} ({})",
" Generating"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
"project's blueprint".if_supports_color(Stderr, |s| s.bold()),
path.display()
.if_supports_color(Stderr, |s| s.bright_blue())
);
}
Event::GeneratingDocFiles { output_path } => {
eprintln!(
"{} in {}",
" Generating documentation files"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
output_path
.to_str()
.unwrap_or("")
.if_supports_color(Stderr, |s| s.bright_blue())
);
}
Event::GeneratingUPLCFor { name, path } => {
eprintln!(
"{} {}.{{{}}}",
" Generating UPLC for"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
path.to_str()
.unwrap_or("")
.if_supports_color(Stderr, |s| s.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 => {
eprintln!(
"{} {}\n",
" Testing"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
"...".if_supports_color(Stderr, |s| s.bold())
);
}
Event::FinishedTests { tests } => {
let (max_mem, max_cpu) = find_max_execution_units(&tests);
for (module, infos) in &group_by_module(&tests) {
let title = module
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.blue())
.to_string();
let tests = infos
.iter()
.map(|eval_info| fmt_test(eval_info, max_mem, max_cpu, true))
.collect::<Vec<String>>()
.join("\n");
let summary = fmt_test_summary(infos, true);
eprintln!(
"{}\n",
pretty::indent(
&pretty::open_box(&title, &tests, &summary, |border| border
.if_supports_color(Stderr, |s| s.bright_black())
.to_string()),
4
)
);
}
}
Event::ResolvingPackages { name } => {
eprintln!(
"{} {}",
" Resolving"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
name.if_supports_color(Stderr, |s| s.bold())
)
}
Event::PackageResolveFallback { name } => {
eprintln!(
"{} {}\n ↳ You're seeing this message because the package version is unpinned and the network is not accessible.",
" Using"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.yellow()),
format!("uncertain local version for {name}")
.if_supports_color(Stderr, |s| s.yellow())
)
}
Event::PackagesDownloaded {
start,
count,
source,
} => {
let elapsed = format!("{:.2}s", start.elapsed().as_millis() as f32 / 1000.);
let msg = match count {
1 => format!("1 package in {elapsed}"),
_ => format!("{count} packages in {elapsed}"),
};
eprintln!(
"{} {} from {source}",
match source {
DownloadSource::Network => " Downloaded",
DownloadSource::Cache => " Fetched",
}
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
msg.if_supports_color(Stderr, |s| s.bold())
)
}
Event::ResolvingVersions => {
eprintln!(
"{}",
" Resolving dependencies"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
)
}
}
}
}
fn fmt_test(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize, styled: bool) -> String {
let EvalInfo {
success,
script,
spent_budget,
logs,
..
} = eval_info;
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, " ");
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 {
logs.iter()
.map(|line| {
format!(
"{arrow} {styled_line}",
arrow = "".if_supports_color(Stderr, |s| s.bright_yellow()),
styled_line = line.if_supports_color(Stderr, |s| s.bright_black())
)
})
.collect::<Vec<_>>()
.join("\n")
};
if logs.is_empty() {
test
} else {
[test, logs].join("\n")
}
}
fn fmt_test_summary(tests: &Vec<&EvalInfo>, styled: bool) -> String {
let (n_passed, n_failed) = tests
.iter()
.fold((0, 0), |(n_passed, n_failed), test_info| {
if test_info.success {
(n_passed + 1, n_failed)
} else {
(n_passed, n_failed + 1)
}
});
format!(
"{} | {} | {}",
pretty::style_if(styled, format!("{} tests", tests.len()), |s| s
.if_supports_color(Stderr, |s| s.bold())
.to_string()),
pretty::style_if(styled, format!("{n_passed} passed"), |s| s
.if_supports_color(Stderr, |s| s.bright_green())
.if_supports_color(Stderr, |s| s.bold())
.to_string()),
pretty::style_if(styled, format!("{n_failed} failed"), |s| s
.if_supports_color(Stderr, |s| s.bright_red())
.if_supports_color(Stderr, |s| s.bold())
.to_string()),
)
}
fn fmt_eval(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize, stream: Stream) -> String {
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();
for eval_info in infos {
let xs: &mut Vec<&EvalInfo> = modules.entry(eval_info.script.module.clone()).or_default();
xs.push(eval_info);
}
modules
}
fn find_max_execution_units(xs: &[EvalInfo]) -> (usize, usize) {
let (max_mem, max_cpu) = xs.iter().fold(
(0, 0),
|(max_mem, max_cpu), EvalInfo { spent_budget, .. }| {
if spent_budget.mem >= max_mem && spent_budget.cpu >= max_cpu {
(spent_budget.mem, spent_budget.cpu)
} else if spent_budget.mem > max_mem {
(spent_budget.mem, max_cpu)
} else if spent_budget.cpu > max_cpu {
(max_mem, spent_budget.cpu)
} else {
(max_mem, max_cpu)
}
},
);
(max_mem.to_string().len(), max_cpu.to_string().len())
}

View File

@ -0,0 +1,225 @@
use crate::{telemetry::Terminal, Project};
use miette::{Diagnostic, IntoDiagnostic};
use notify::{Event, RecursiveMode, Watcher};
use owo_colors::{OwoColorize, Stream::Stderr};
use std::{
collections::VecDeque,
env,
ffi::OsStr,
fmt::{self, Display},
path::Path,
sync::{Arc, Mutex},
};
#[derive(Debug, Diagnostic, thiserror::Error)]
enum ExitFailure {
#[error("")]
ExitFailure,
}
impl ExitFailure {
fn into_report() -> miette::Report {
ExitFailure::ExitFailure.into()
}
}
struct Summary {
warning_count: usize,
error_count: usize,
}
impl Display for Summary {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&format!(
" {} {} {}, {} {}",
"Summary"
.if_supports_color(Stderr, |s| s.purple())
.if_supports_color(Stderr, |s| s.bold()),
self.error_count,
if self.error_count == 1 {
"error"
} else {
"errors"
}
.if_supports_color(Stderr, |s| s.red())
.if_supports_color(Stderr, |s| s.bold()),
self.warning_count,
if self.warning_count == 1 {
"warning"
} else {
"warnings"
}
.if_supports_color(Stderr, |s| s.yellow())
.if_supports_color(Stderr, |s| s.bold()),
))
}
}
/// A default filter for file events that catches the most relevant "source" changes
pub fn default_filter(evt: &Event) -> bool {
// Only watch for changes to .ak and aiken.toml files, and ignore the build directory
let source_file = evt
.paths
.iter()
.any(|p| p.extension() == Some(OsStr::new("ak")) || p.ends_with("aiken.toml"));
let build_dir = evt
.paths
.iter()
.all(|p| p.ancestors().any(|a| a.ends_with("build")));
match evt.kind {
notify::EventKind::Any => true,
notify::EventKind::Create(_)
| notify::EventKind::Modify(_)
| notify::EventKind::Remove(_) => source_file && !build_dir,
_ => false,
}
}
pub fn with_project<A>(directory: Option<&Path>, deny: bool, mut action: A) -> miette::Result<()>
where
A: FnMut(&mut Project<Terminal>) -> Result<(), Vec<crate::error::Error>>,
{
let project_path = if let Some(d) = directory {
d.to_path_buf()
} else {
env::current_dir().into_diagnostic()?
};
let mut project = match Project::new(project_path, Terminal) {
Ok(p) => Ok(p),
Err(e) => {
e.report();
Err(ExitFailure::into_report())
}
}?;
let build_result = action(&mut project);
let warnings = project.warnings();
let warning_count = warnings.len();
for warning in &warnings {
warning.report()
}
if let Err(errs) = build_result {
for err in &errs {
err.report()
}
eprintln!(
"{}",
Summary {
warning_count,
error_count: errs.len(),
}
);
return Err(ExitFailure::into_report());
} else {
eprintln!(
"{}",
Summary {
error_count: 0,
warning_count
}
);
}
if warning_count > 0 && deny {
Err(ExitFailure::into_report())
} else {
Ok(())
}
}
/// Run a function each time a file in the project changes
///
/// ```text
/// // Note: doctest disabled, because aiken_project doesn't have an implementation of EventListener I can use
/// use aiken_project::watch::{watch_project, default_filter};
/// use aiken_project::{Project};
/// watch_project(None, Terminal, default_filter, 500, |project| {
/// println!("Project changed!");
/// Ok(())
/// });
/// ```
pub fn watch_project<F, A>(
directory: Option<&Path>,
filter: F,
debounce: u32,
mut action: A,
) -> miette::Result<()>
where
F: Fn(&Event) -> bool,
A: FnMut(&mut Project<Terminal>) -> Result<(), Vec<crate::error::Error>>,
{
let project_path = directory
.map(|p| p.to_path_buf())
.unwrap_or(env::current_dir().into_diagnostic()?);
// Set up a queue for events, primarily so we can debounce on related events
let queue = Arc::new(Mutex::new(VecDeque::new()));
// Run the action once, to start
queue
.lock()
.expect("lock queue")
.push_back(Event::default());
// Spawn a file-watcher that will put each change event on the queue
let queue_write = queue.clone();
let mut watcher = notify::recommended_watcher(move |res: notify::Result<Event>| {
match res {
Ok(event) => queue_write
.lock()
.expect("lock queue")
.push_back(event.clone()),
Err(e) => {
// TODO: miette diagnostic?
println!(
"Encountered an error while monitoring for file changes: {:?}",
e
)
}
};
})
.into_diagnostic()?;
// Start watching for any changes in the project directory
let _ = watcher.watch(project_path.as_path(), RecursiveMode::Recursive);
// And then start reading from the queue
let queue_read = queue.clone();
loop {
// We sleep for the debounce interval, because notify will dump 12 related events into the queue all at once
std::thread::sleep(std::time::Duration::from_millis(debounce.into()));
// Grab the lock, and pop all events except the last one off the queue
let mut queue = queue_read.lock().expect("lock queue");
let mut latest = None;
// debounce the events, and ignore build/lock changes, because they come in in large batches
while let Some(evt) = queue.pop_back() {
// check if this event is meaningful to the caller
if !filter(&evt) {
continue;
}
latest = Some(evt);
}
// release the lock here, in case other events come in
drop(queue);
// If we have an event that survived the filter, then we can construct the project and invoke the action
if latest.is_some() {
print!("{esc}c", esc = 27 as char);
println!(
"{} ...",
" Watching"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
);
with_project(directory, false, &mut action).unwrap_or(())
}
}
}

View File

@ -1,5 +1,5 @@
use crate::with_project;
use aiken_lang::ast::Tracing;
use aiken_project::watch::with_project;
use std::path::PathBuf;
/// Compute a validator's address.
@ -34,7 +34,7 @@ pub fn exec(
rebuild,
}: Args,
) -> miette::Result<()> {
with_project(directory, false, |p| {
with_project(directory.as_deref(), false, |p| {
if rebuild {
p.build(false, Tracing::NoTraces)?;
}

View File

@ -1,4 +1,3 @@
use crate::with_project;
use aiken_project::{
blueprint::{
self,
@ -7,6 +6,7 @@ use aiken_project::{
},
error::Error,
pretty::multiline,
watch::with_project,
};
use inquire;
use num_bigint::BigInt;

View File

@ -1,5 +1,5 @@
use crate::with_project;
use aiken_lang::ast::Tracing;
use aiken_project::watch::with_project;
use std::path::PathBuf;
/// Compute a validator's hash
@ -29,7 +29,7 @@ pub fn exec(
rebuild,
}: Args,
) -> miette::Result<()> {
with_project(directory, false, |p| {
with_project(directory.as_deref(), false, |p| {
if rebuild {
p.build(false, Tracing::NoTraces)?;
}

View File

@ -1,5 +1,5 @@
use crate::with_project;
use aiken_lang::ast::Tracing;
use aiken_project::watch::with_project;
use std::path::PathBuf;
/// Compute a minting scripts Policy ID
@ -29,7 +29,7 @@ pub fn exec(
rebuild,
}: Args,
) -> miette::Result<()> {
with_project(directory, false, |p| {
with_project(directory.as_deref(), false, |p| {
if rebuild {
p.build(false, Tracing::NoTraces)?;
}

View File

@ -1,3 +1,4 @@
use aiken_project::watch::{self, watch_project, with_project};
use std::path::PathBuf;
#[derive(clap::Args)]
@ -10,6 +11,10 @@ pub struct Args {
#[clap(short = 'D', long)]
deny: bool,
/// When enabled, re-run the command on file changes instead of exiting
#[clap(short, long)]
watch: bool,
/// Also dump textual uplc
#[clap(short, long)]
uplc: bool,
@ -23,9 +28,18 @@ pub fn exec(
Args {
directory,
deny,
watch,
uplc,
keep_traces,
}: Args,
) -> miette::Result<()> {
crate::with_project(directory, deny, |p| p.build(uplc, keep_traces.into()))
if watch {
watch_project(directory.as_deref(), watch::default_filter, 500, |p| {
p.build(uplc, keep_traces.into())
})
} else {
with_project(directory.as_deref(), deny, |p| {
p.build(uplc, keep_traces.into())
})
}
}

View File

@ -1,3 +1,4 @@
use aiken_project::watch::{self, watch_project, with_project};
use std::path::PathBuf;
#[derive(clap::Args)]
@ -18,6 +19,10 @@ pub struct Args {
#[clap(long)]
debug: bool,
/// When enabled, re-run the command on file changes instead of exiting
#[clap(long)]
watch: bool,
/// 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 test with `-m "aiken/list.{map}"` or `-m "aiken/option.{flatten_1}"`
@ -43,15 +48,29 @@ pub fn exec(
match_tests,
exact_match,
no_traces,
watch,
..
}: Args,
) -> miette::Result<()> {
crate::with_project(directory, deny, |p| {
p.check(
skip_tests,
match_tests.clone(),
debug,
exact_match,
(!no_traces).into(),
)
})
if watch {
watch_project(directory.as_deref(), watch::default_filter, 500, |p| {
p.check(
skip_tests,
match_tests.clone(),
debug,
exact_match,
(!no_traces).into(),
)
})
} else {
with_project(directory.as_deref(), deny, |p| {
p.check(
skip_tests,
match_tests.clone(),
debug,
exact_match,
(!no_traces).into(),
)
})
}
}

View File

@ -1,3 +1,4 @@
use aiken_project::watch::{self, watch_project, with_project};
use std::path::PathBuf;
#[derive(clap::Args)]
@ -10,6 +11,10 @@ pub struct Args {
#[clap(short = 'D', long)]
deny: bool,
/// When enabled, re-run the command on file changes instead of exiting
#[clap(short, long)]
watch: bool,
/// Output directory for the documentation
#[clap(short = 'o', long)]
destination: Option<PathBuf>,
@ -19,8 +24,15 @@ pub fn exec(
Args {
directory,
deny,
watch,
destination,
}: Args,
) -> miette::Result<()> {
crate::with_project(directory, deny, |p| p.docs(destination.clone()))
if watch {
watch_project(directory.as_deref(), watch::default_filter, 500, |p| {
p.docs(destination.clone())
})
} else {
with_project(directory.as_deref(), deny, |p| p.docs(destination.clone()))
}
}

View File

@ -1,431 +0,0 @@
use aiken_project::{
pretty,
script::EvalInfo,
telemetry::{self, DownloadSource},
Project,
};
use miette::IntoDiagnostic;
use owo_colors::{
OwoColorize,
Stream::{self, Stderr},
};
use std::{collections::BTreeMap, env, path::PathBuf, process};
use uplc::machine::cost_model::ExBudget;
pub mod cmd;
pub fn with_project<A>(directory: Option<PathBuf>, deny: bool, mut action: A) -> miette::Result<()>
where
A: FnMut(&mut Project<Terminal>) -> Result<(), Vec<aiken_project::error::Error>>,
{
let project_path = if let Some(d) = directory {
d
} else {
env::current_dir().into_diagnostic()?
};
let mut project = match Project::new(project_path, Terminal) {
Ok(p) => p,
Err(e) => {
e.report();
process::exit(1);
}
};
let build_result = action(&mut project);
let warnings = project.warnings();
let warning_count = warnings.len();
for warning in &warnings {
warning.report()
}
let plural = if warning_count == 1 { "" } else { "s" };
if let Err(errs) = build_result {
for err in &errs {
err.report()
}
eprintln!(
"\n{}",
"Summary"
.if_supports_color(Stderr, |s| s.purple())
.if_supports_color(Stderr, |s| s.bold())
);
let warning_text = format!("{warning_count} warning{plural}");
let plural = if errs.len() == 1 { "" } else { "s" };
let error_text = format!("{} error{}", errs.len(), plural);
let full_summary = format!(
" {}, {}",
error_text.if_supports_color(Stderr, |s| s.red()),
warning_text.if_supports_color(Stderr, |s| s.yellow())
);
eprintln!("{full_summary}");
process::exit(1);
} else {
eprintln!(
"\n{}",
"Summary"
.if_supports_color(Stderr, |s| s.purple())
.if_supports_color(Stderr, |s| s.bold())
);
let warning_text = format!("{warning_count} warning{plural}");
eprintln!(
" 0 errors, {}",
warning_text.if_supports_color(Stderr, |s| s.yellow()),
);
}
if warning_count > 0 && deny {
process::exit(1);
}
Ok(())
}
#[derive(Debug, Default, Clone, Copy)]
pub struct Terminal;
impl telemetry::EventListener for Terminal {
fn handle_event(&self, event: telemetry::Event) {
match event {
telemetry::Event::StartingCompilation {
name,
version,
root,
} => {
eprintln!(
"{} {} {} ({})",
" Compiling"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
name.if_supports_color(Stderr, |s| s.bold()),
version,
root.display()
.if_supports_color(Stderr, |s| s.bright_blue())
);
}
telemetry::Event::BuildingDocumentation {
name,
version,
root,
} => {
eprintln!(
"{} {} {} ({})",
" Generating documentation"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
name.if_supports_color(Stderr, |s| s.bold()),
version,
root.to_str()
.unwrap_or("")
.if_supports_color(Stderr, |s| s.bright_blue())
);
}
telemetry::Event::WaitingForBuildDirLock => {
eprintln!(
"{}",
"Waiting for build directory lock ..."
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple())
);
}
telemetry::Event::DumpingUPLC { path } => {
eprintln!(
"{} {} ({})",
" Exporting"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
"UPLC".if_supports_color(Stderr, |s| s.bold()),
path.display()
.if_supports_color(Stderr, |s| s.bright_blue())
);
}
telemetry::Event::GeneratingBlueprint { path } => {
eprintln!(
"{} {} ({})",
" Generating"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
"project's blueprint".if_supports_color(Stderr, |s| s.bold()),
path.display()
.if_supports_color(Stderr, |s| s.bright_blue())
);
}
telemetry::Event::GeneratingDocFiles { output_path } => {
eprintln!(
"{} in {}",
" Generating documentation files"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
output_path
.to_str()
.unwrap_or("")
.if_supports_color(Stderr, |s| s.bright_blue())
);
}
telemetry::Event::GeneratingUPLCFor { name, path } => {
eprintln!(
"{} {}.{{{}}}",
" Generating UPLC for"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
path.to_str()
.unwrap_or("")
.if_supports_color(Stderr, |s| s.blue()),
name.if_supports_color(Stderr, |s| s.bright_blue()),
);
}
telemetry::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))
}
}
telemetry::Event::RunningTests => {
eprintln!(
"{} {}\n",
" Testing"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
"...".if_supports_color(Stderr, |s| s.bold())
);
}
telemetry::Event::FinishedTests { tests } => {
let (max_mem, max_cpu) = find_max_execution_units(&tests);
for (module, infos) in &group_by_module(&tests) {
let title = module
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.blue())
.to_string();
let tests = infos
.iter()
.map(|eval_info| fmt_test(eval_info, max_mem, max_cpu, true))
.collect::<Vec<String>>()
.join("\n");
let summary = fmt_test_summary(infos, true);
eprintln!(
"{}\n",
pretty::indent(
&pretty::open_box(&title, &tests, &summary, |border| border
.if_supports_color(Stderr, |s| s.bright_black())
.to_string()),
4
)
);
}
}
telemetry::Event::ResolvingPackages { name } => {
eprintln!(
"{} {}",
" Resolving"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
name.if_supports_color(Stderr, |s| s.bold())
)
}
telemetry::Event::PackageResolveFallback { name } => {
eprintln!(
"{} {}\n ↳ You're seeing this message because the package version is unpinned and the network is not accessible.",
" Using"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.yellow()),
format!("uncertain local version for {name}")
.if_supports_color(Stderr, |s| s.yellow())
)
}
telemetry::Event::PackagesDownloaded {
start,
count,
source,
} => {
let elapsed = format!("{:.2}s", start.elapsed().as_millis() as f32 / 1000.);
let msg = match count {
1 => format!("1 package in {elapsed}"),
_ => format!("{count} packages in {elapsed}"),
};
eprintln!(
"{} {} from {source}",
match source {
DownloadSource::Network => " Downloaded",
DownloadSource::Cache => " Fetched",
}
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
msg.if_supports_color(Stderr, |s| s.bold())
)
}
telemetry::Event::ResolvingVersions => {
eprintln!(
"{}",
" Resolving dependencies"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
)
}
}
}
}
fn fmt_test(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize, styled: bool) -> String {
let EvalInfo {
success,
script,
spent_budget,
logs,
..
} = eval_info;
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, " ");
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 {
logs.iter()
.map(|line| {
format!(
"{arrow} {styled_line}",
arrow = "".if_supports_color(Stderr, |s| s.bright_yellow()),
styled_line = line.if_supports_color(Stderr, |s| s.bright_black())
)
})
.collect::<Vec<_>>()
.join("\n")
};
if logs.is_empty() {
test
} else {
[test, logs].join("\n")
}
}
fn fmt_test_summary(tests: &Vec<&EvalInfo>, styled: bool) -> String {
let (n_passed, n_failed) = tests
.iter()
.fold((0, 0), |(n_passed, n_failed), test_info| {
if test_info.success {
(n_passed + 1, n_failed)
} else {
(n_passed, n_failed + 1)
}
});
format!(
"{} | {} | {}",
pretty::style_if(styled, format!("{} tests", tests.len()), |s| s
.if_supports_color(Stderr, |s| s.bold())
.to_string()),
pretty::style_if(styled, format!("{n_passed} passed"), |s| s
.if_supports_color(Stderr, |s| s.bright_green())
.if_supports_color(Stderr, |s| s.bold())
.to_string()),
pretty::style_if(styled, format!("{n_failed} failed"), |s| s
.if_supports_color(Stderr, |s| s.bright_red())
.if_supports_color(Stderr, |s| s.bold())
.to_string()),
)
}
fn fmt_eval(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize, stream: Stream) -> String {
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();
for eval_info in infos {
let xs: &mut Vec<&EvalInfo> = modules.entry(eval_info.script.module.clone()).or_default();
xs.push(eval_info);
}
modules
}
fn find_max_execution_units(xs: &[EvalInfo]) -> (usize, usize) {
let (max_mem, max_cpu) = xs.iter().fold(
(0, 0),
|(max_mem, max_cpu), EvalInfo { spent_budget, .. }| {
if spent_budget.mem >= max_mem && spent_budget.cpu >= max_cpu {
(spent_budget.mem, spent_budget.cpu)
} else if spent_budget.mem > max_mem {
(spent_budget.mem, max_cpu)
} else if spent_budget.cpu > max_cpu {
(max_mem, spent_budget.cpu)
} else {
(max_mem, max_cpu)
}
},
);
(max_mem.to_string().len(), max_cpu.to_string().len())
}

View File

@ -1,17 +1,19 @@
use aiken::cmd::{
use aiken_project::{config, pretty};
use cmd::{
blueprint::{self, address},
build, check, completion, docs, fmt, lsp, new,
packages::{self, add},
tx, uplc, Cmd,
};
use aiken_project::{config, pretty};
use owo_colors::OwoColorize;
use std::process;
fn main() -> miette::Result<()> {
mod cmd;
fn main() {
panic_handler();
match Cmd::default() {
let result = match Cmd::default() {
Cmd::New(args) => new::exec(args),
Cmd::Fmt(args) => fmt::exec(args),
Cmd::Build(args) => build::exec(args),
@ -25,6 +27,11 @@ fn main() -> miette::Result<()> {
Cmd::Tx(sub_cmd) => tx::exec(sub_cmd),
Cmd::Uplc(sub_cmd) => uplc::exec(sub_cmd),
Cmd::Completion(sub_cmd) => completion::exec(sub_cmd),
};
match result {
Ok(()) => (),
Err(_) => process::exit(1),
}
}

View File

@ -240,9 +240,12 @@ impl Machine {
env,
t.clone(),
)),
None => todo!(),
None => Err(Error::MissingCaseBranch(
branches,
Value::Constr { tag, fields },
)),
},
_ => todo!("return a proper evaluation error"),
v => Err(Error::NonConstrScrutinized(v)),
},
}
}

View File

@ -186,12 +186,12 @@ impl Default for MachineCosts {
},
// Placeholder values
constr: ExBudget {
mem: 30000000000,
cpu: 30000000000,
mem: 100,
cpu: 23000,
},
case: ExBudget {
mem: 30000000000,
cpu: 30000000000,
mem: 100,
cpu: 23000,
},
}
}

View File

@ -20,6 +20,10 @@ pub enum Error {
NonPolymorphicInstantiation(Value),
#[error("Attempted to apply a non-function:\n\n{0:#?} to argument:\n\n{1:#?}")]
NonFunctionalApplication(Value, Value),
#[error("Attempted to case a non-const:\n\n{0:#?}")]
NonConstrScrutinized(Value),
#[error("Cases: {0:#?}\n\n are missing branch for constr:\n\n{1:#?}")]
MissingCaseBranch(Vec<Term<NamedDeBruijn>>, Value),
#[error("Type mismatch expected '{0}' got '{1}'")]
TypeMismatch(Type, Type),
#[error("Type mismatch expected '(list a)' got '{0}'")]
@ -36,6 +40,7 @@ pub enum Error {
NotAConstant(Value),
#[error("The evaluation never reached a final state")]
MachineNeverReachedDone,
#[error("Decoding utf8")]
Utf8(#[from] FromUtf8Error),
#[error("Out of Bounds\n\nindex: {}\nbytestring: {}\npossible: 0 - {}", .0, hex::encode(.1), .1.len() - 1)]

View File

@ -89,8 +89,6 @@ impl BuiltinRuntime {
}
pub fn push(&mut self, arg: Value) -> Result<(), Error> {
self.fun.check_type(&arg, &self.args)?;
self.args.push(arg);
Ok(())
@ -264,171 +262,6 @@ impl DefaultFunction {
}
}
pub fn check_type(&self, arg: &Value, args: &[Value]) -> Result<(), Error> {
match self {
DefaultFunction::AddInteger => arg.expect_type(Type::Integer),
DefaultFunction::SubtractInteger => arg.expect_type(Type::Integer),
DefaultFunction::MultiplyInteger => arg.expect_type(Type::Integer),
DefaultFunction::DivideInteger => arg.expect_type(Type::Integer),
DefaultFunction::QuotientInteger => arg.expect_type(Type::Integer),
DefaultFunction::RemainderInteger => arg.expect_type(Type::Integer),
DefaultFunction::ModInteger => arg.expect_type(Type::Integer),
DefaultFunction::EqualsInteger => arg.expect_type(Type::Integer),
DefaultFunction::LessThanInteger => arg.expect_type(Type::Integer),
DefaultFunction::LessThanEqualsInteger => arg.expect_type(Type::Integer),
DefaultFunction::AppendByteString => arg.expect_type(Type::ByteString),
DefaultFunction::ConsByteString => {
if args.is_empty() {
arg.expect_type(Type::Integer)
} else {
arg.expect_type(Type::ByteString)
}
}
DefaultFunction::SliceByteString => {
if args.len() < 2 {
arg.expect_type(Type::Integer)
} else {
arg.expect_type(Type::ByteString)
}
}
DefaultFunction::LengthOfByteString => arg.expect_type(Type::ByteString),
DefaultFunction::IndexByteString => {
if args.is_empty() {
arg.expect_type(Type::ByteString)
} else {
arg.expect_type(Type::Integer)
}
}
DefaultFunction::EqualsByteString => arg.expect_type(Type::ByteString),
DefaultFunction::LessThanByteString => arg.expect_type(Type::ByteString),
DefaultFunction::LessThanEqualsByteString => arg.expect_type(Type::ByteString),
DefaultFunction::Sha2_256 => arg.expect_type(Type::ByteString),
DefaultFunction::Sha3_256 => arg.expect_type(Type::ByteString),
DefaultFunction::Blake2b_224 => arg.expect_type(Type::ByteString),
DefaultFunction::Blake2b_256 => arg.expect_type(Type::ByteString),
DefaultFunction::Keccak_256 => arg.expect_type(Type::ByteString),
DefaultFunction::VerifyEd25519Signature => arg.expect_type(Type::ByteString),
DefaultFunction::VerifyEcdsaSecp256k1Signature => arg.expect_type(Type::ByteString),
DefaultFunction::VerifySchnorrSecp256k1Signature => arg.expect_type(Type::ByteString),
DefaultFunction::AppendString => arg.expect_type(Type::String),
DefaultFunction::EqualsString => arg.expect_type(Type::String),
DefaultFunction::EncodeUtf8 => arg.expect_type(Type::String),
DefaultFunction::DecodeUtf8 => arg.expect_type(Type::ByteString),
DefaultFunction::IfThenElse => {
if args.is_empty() {
arg.expect_type(Type::Bool)
} else {
Ok(())
}
}
DefaultFunction::ChooseUnit => {
if args.is_empty() {
arg.expect_type(Type::Unit)
} else {
Ok(())
}
}
DefaultFunction::Trace => {
if args.is_empty() {
arg.expect_type(Type::String)
} else {
Ok(())
}
}
DefaultFunction::FstPair => arg.expect_pair(),
DefaultFunction::SndPair => arg.expect_pair(),
DefaultFunction::ChooseList => {
if args.is_empty() {
arg.expect_list()
} else {
Ok(())
}
}
DefaultFunction::MkCons => {
if args.is_empty() {
Ok(())
} else {
let first = &args[0];
arg.expect_type(Type::List(Rc::new(first.try_into()?)))
}
}
DefaultFunction::HeadList => arg.expect_list(),
DefaultFunction::TailList => arg.expect_list(),
DefaultFunction::NullList => arg.expect_list(),
DefaultFunction::ChooseData => {
if args.is_empty() {
arg.expect_type(Type::Data)
} else {
Ok(())
}
}
DefaultFunction::ConstrData => {
if args.is_empty() {
arg.expect_type(Type::Integer)
} else {
arg.expect_type(Type::List(Rc::new(Type::Data)))
}
}
DefaultFunction::MapData => arg.expect_type(Type::List(Rc::new(Type::Pair(
Rc::new(Type::Data),
Rc::new(Type::Data),
)))),
DefaultFunction::ListData => arg.expect_type(Type::List(Rc::new(Type::Data))),
DefaultFunction::IData => arg.expect_type(Type::Integer),
DefaultFunction::BData => arg.expect_type(Type::ByteString),
DefaultFunction::UnConstrData => arg.expect_type(Type::Data),
DefaultFunction::UnMapData => arg.expect_type(Type::Data),
DefaultFunction::UnListData => arg.expect_type(Type::Data),
DefaultFunction::UnIData => arg.expect_type(Type::Data),
DefaultFunction::UnBData => arg.expect_type(Type::Data),
DefaultFunction::EqualsData => arg.expect_type(Type::Data),
DefaultFunction::SerialiseData => arg.expect_type(Type::Data),
DefaultFunction::MkPairData => arg.expect_type(Type::Data),
DefaultFunction::MkNilData => arg.expect_type(Type::Unit),
DefaultFunction::MkNilPairData => arg.expect_type(Type::Unit),
DefaultFunction::Bls12_381_G1_Add => arg.expect_type(Type::Bls12_381G1Element),
DefaultFunction::Bls12_381_G1_Neg => arg.expect_type(Type::Bls12_381G1Element),
DefaultFunction::Bls12_381_G1_ScalarMul => {
if args.is_empty() {
arg.expect_type(Type::Integer)
} else {
arg.expect_type(Type::Bls12_381G1Element)
}
}
DefaultFunction::Bls12_381_G1_Equal => arg.expect_type(Type::Bls12_381G1Element),
DefaultFunction::Bls12_381_G1_Compress => arg.expect_type(Type::Bls12_381G1Element),
DefaultFunction::Bls12_381_G1_Uncompress => arg.expect_type(Type::ByteString),
DefaultFunction::Bls12_381_G1_HashToGroup => arg.expect_type(Type::ByteString),
DefaultFunction::Bls12_381_G2_Add => arg.expect_type(Type::Bls12_381G2Element),
DefaultFunction::Bls12_381_G2_Neg => arg.expect_type(Type::Bls12_381G2Element),
DefaultFunction::Bls12_381_G2_ScalarMul => {
if args.is_empty() {
arg.expect_type(Type::Integer)
} else {
arg.expect_type(Type::Bls12_381G2Element)
}
}
DefaultFunction::Bls12_381_G2_Equal => arg.expect_type(Type::Bls12_381G2Element),
DefaultFunction::Bls12_381_G2_Compress => arg.expect_type(Type::Bls12_381G2Element),
DefaultFunction::Bls12_381_G2_Uncompress => arg.expect_type(Type::ByteString),
DefaultFunction::Bls12_381_G2_HashToGroup => arg.expect_type(Type::ByteString),
DefaultFunction::Bls12_381_MillerLoop => {
if args.is_empty() {
arg.expect_type(Type::Bls12_381G1Element)
} else {
arg.expect_type(Type::Bls12_381G2Element)
}
}
DefaultFunction::Bls12_381_MulMlResult => arg.expect_type(Type::Bls12_381MlResult),
DefaultFunction::Bls12_381_FinalVerify => arg.expect_type(Type::Bls12_381MlResult),
}
}
// This should be safe because we've already checked
// the types of the args as they were pushed. Although
// the unreachables look ugly, it's the reality of the situation.
pub fn call(
&self,
semantics: BuiltinSemantics,
@ -437,8 +270,8 @@ impl DefaultFunction {
) -> Result<Value, Error> {
match self {
DefaultFunction::AddInteger => {
let arg1 = args[0].unwrap_integer();
let arg2 = args[1].unwrap_integer();
let arg1 = args[0].unwrap_integer()?;
let arg2 = args[1].unwrap_integer()?;
let result = arg1 + arg2;
@ -447,8 +280,8 @@ impl DefaultFunction {
Ok(value)
}
DefaultFunction::SubtractInteger => {
let arg1 = args[0].unwrap_integer();
let arg2 = args[1].unwrap_integer();
let arg1 = args[0].unwrap_integer()?;
let arg2 = args[1].unwrap_integer()?;
let result = arg1 - arg2;
@ -457,8 +290,8 @@ impl DefaultFunction {
Ok(value)
}
DefaultFunction::MultiplyInteger => {
let arg1 = args[0].unwrap_integer();
let arg2 = args[1].unwrap_integer();
let arg1 = args[0].unwrap_integer()?;
let arg2 = args[1].unwrap_integer()?;
let result = arg1 * arg2;
@ -467,8 +300,8 @@ impl DefaultFunction {
Ok(value)
}
DefaultFunction::DivideInteger => {
let arg1 = args[0].unwrap_integer();
let arg2 = args[1].unwrap_integer();
let arg1 = args[0].unwrap_integer()?;
let arg2 = args[1].unwrap_integer()?;
if *arg2 != 0.into() {
let (result, _) = arg1.div_mod_floor(arg2);
@ -481,8 +314,8 @@ impl DefaultFunction {
}
}
DefaultFunction::QuotientInteger => {
let arg1 = args[0].unwrap_integer();
let arg2 = args[1].unwrap_integer();
let arg1 = args[0].unwrap_integer()?;
let arg2 = args[1].unwrap_integer()?;
if *arg2 != 0.into() {
let (result, _) = arg1.div_rem(arg2);
@ -495,8 +328,8 @@ impl DefaultFunction {
}
}
DefaultFunction::RemainderInteger => {
let arg1 = args[0].unwrap_integer();
let arg2 = args[1].unwrap_integer();
let arg1 = args[0].unwrap_integer()?;
let arg2 = args[1].unwrap_integer()?;
if *arg2 != 0.into() {
let (_, result) = arg1.div_rem(arg2);
@ -509,8 +342,8 @@ impl DefaultFunction {
}
}
DefaultFunction::ModInteger => {
let arg1 = args[0].unwrap_integer();
let arg2 = args[1].unwrap_integer();
let arg1 = args[0].unwrap_integer()?;
let arg2 = args[1].unwrap_integer()?;
if *arg2 != 0.into() {
let (_, result) = arg1.div_mod_floor(arg2);
@ -523,32 +356,32 @@ impl DefaultFunction {
}
}
DefaultFunction::EqualsInteger => {
let arg1 = args[0].unwrap_integer();
let arg2 = args[1].unwrap_integer();
let arg1 = args[0].unwrap_integer()?;
let arg2 = args[1].unwrap_integer()?;
let value = Value::bool(arg1 == arg2);
Ok(value)
}
DefaultFunction::LessThanInteger => {
let arg1 = args[0].unwrap_integer();
let arg2 = args[1].unwrap_integer();
let arg1 = args[0].unwrap_integer()?;
let arg2 = args[1].unwrap_integer()?;
let value = Value::bool(arg1 < arg2);
Ok(value)
}
DefaultFunction::LessThanEqualsInteger => {
let arg1 = args[0].unwrap_integer();
let arg2 = args[1].unwrap_integer();
let arg1 = args[0].unwrap_integer()?;
let arg2 = args[1].unwrap_integer()?;
let value = Value::bool(arg1 <= arg2);
Ok(value)
}
DefaultFunction::AppendByteString => {
let arg1 = args[0].unwrap_byte_string();
let arg2 = args[1].unwrap_byte_string();
let arg1 = args[0].unwrap_byte_string()?;
let arg2 = args[1].unwrap_byte_string()?;
let result = arg1.iter().copied().chain(arg2.iter().copied()).collect();
@ -557,8 +390,8 @@ impl DefaultFunction {
Ok(value)
}
DefaultFunction::ConsByteString => {
let arg1 = args[0].unwrap_integer();
let arg2 = args[1].unwrap_byte_string();
let arg1 = args[0].unwrap_integer()?;
let arg2 = args[1].unwrap_byte_string()?;
let byte: u8 = match semantics {
BuiltinSemantics::V1 => {
@ -584,9 +417,9 @@ impl DefaultFunction {
Ok(value)
}
DefaultFunction::SliceByteString => {
let arg1 = args[0].unwrap_integer();
let arg2 = args[1].unwrap_integer();
let arg3 = args[2].unwrap_byte_string();
let arg1 = args[0].unwrap_integer()?;
let arg2 = args[1].unwrap_integer()?;
let arg3 = args[2].unwrap_byte_string()?;
let skip: usize = if arg1.lt(&0.into()) {
0
@ -606,15 +439,15 @@ impl DefaultFunction {
Ok(value)
}
DefaultFunction::LengthOfByteString => {
let arg1 = args[0].unwrap_byte_string();
let arg1 = args[0].unwrap_byte_string()?;
let value = Value::integer(arg1.len().into());
Ok(value)
}
DefaultFunction::IndexByteString => {
let arg1 = args[0].unwrap_byte_string();
let arg2 = args[1].unwrap_integer();
let arg1 = args[0].unwrap_byte_string()?;
let arg2 = args[1].unwrap_integer()?;
let index: i128 = arg2.try_into().unwrap();
@ -629,24 +462,24 @@ impl DefaultFunction {
}
}
DefaultFunction::EqualsByteString => {
let arg1 = args[0].unwrap_byte_string();
let arg2 = args[1].unwrap_byte_string();
let arg1 = args[0].unwrap_byte_string()?;
let arg2 = args[1].unwrap_byte_string()?;
let value = Value::bool(arg1 == arg2);
Ok(value)
}
DefaultFunction::LessThanByteString => {
let arg1 = args[0].unwrap_byte_string();
let arg2 = args[1].unwrap_byte_string();
let arg1 = args[0].unwrap_byte_string()?;
let arg2 = args[1].unwrap_byte_string()?;
let value = Value::bool(arg1 < arg2);
Ok(value)
}
DefaultFunction::LessThanEqualsByteString => {
let arg1 = args[0].unwrap_byte_string();
let arg2 = args[1].unwrap_byte_string();
let arg1 = args[0].unwrap_byte_string()?;
let arg2 = args[1].unwrap_byte_string()?;
let value = Value::bool(arg1 <= arg2);
@ -655,7 +488,7 @@ impl DefaultFunction {
DefaultFunction::Sha2_256 => {
use cryptoxide::{digest::Digest, sha2::Sha256};
let arg1 = args[0].unwrap_byte_string();
let arg1 = args[0].unwrap_byte_string()?;
let mut hasher = Sha256::new();
@ -672,7 +505,7 @@ impl DefaultFunction {
DefaultFunction::Sha3_256 => {
use cryptoxide::{digest::Digest, sha3::Sha3_256};
let arg1 = args[0].unwrap_byte_string();
let arg1 = args[0].unwrap_byte_string()?;
let mut hasher = Sha3_256::new();
@ -690,7 +523,7 @@ impl DefaultFunction {
DefaultFunction::Blake2b_224 => {
use cryptoxide::{blake2b::Blake2b, digest::Digest};
let arg1 = args[0].unwrap_byte_string();
let arg1 = args[0].unwrap_byte_string()?;
let mut digest = [0u8; 28];
let mut context = Blake2b::new(28);
@ -705,7 +538,7 @@ impl DefaultFunction {
DefaultFunction::Blake2b_256 => {
use cryptoxide::{blake2b::Blake2b, digest::Digest};
let arg1 = args[0].unwrap_byte_string();
let arg1 = args[0].unwrap_byte_string()?;
let mut digest = [0u8; 32];
let mut context = Blake2b::new(32);
@ -720,7 +553,7 @@ impl DefaultFunction {
DefaultFunction::Keccak_256 => {
use cryptoxide::{digest::Digest, sha3::Keccak256};
let arg1 = args[0].unwrap_byte_string();
let arg1 = args[0].unwrap_byte_string()?;
let mut hasher = Keccak256::new();
@ -737,9 +570,9 @@ impl DefaultFunction {
DefaultFunction::VerifyEd25519Signature => {
use cryptoxide::ed25519;
let public_key = args[0].unwrap_byte_string();
let message = args[1].unwrap_byte_string();
let signature = args[2].unwrap_byte_string();
let public_key = args[0].unwrap_byte_string()?;
let message = args[1].unwrap_byte_string()?;
let signature = args[2].unwrap_byte_string()?;
let public_key: [u8; 32] = public_key
.clone()
@ -758,37 +591,37 @@ impl DefaultFunction {
Ok(value)
}
DefaultFunction::VerifyEcdsaSecp256k1Signature => {
let public_key = args[0].unwrap_byte_string();
let message = args[1].unwrap_byte_string();
let signature = args[2].unwrap_byte_string();
let public_key = args[0].unwrap_byte_string()?;
let message = args[1].unwrap_byte_string()?;
let signature = args[2].unwrap_byte_string()?;
verify_ecdsa(public_key, message, signature)
}
DefaultFunction::VerifySchnorrSecp256k1Signature => {
let public_key = args[0].unwrap_byte_string();
let message = args[1].unwrap_byte_string();
let signature = args[2].unwrap_byte_string();
let public_key = args[0].unwrap_byte_string()?;
let message = args[1].unwrap_byte_string()?;
let signature = args[2].unwrap_byte_string()?;
verify_schnorr(public_key, message, signature)
}
DefaultFunction::AppendString => {
let arg1 = args[0].unwrap_string();
let arg2 = args[1].unwrap_string();
let arg1 = args[0].unwrap_string()?;
let arg2 = args[1].unwrap_string()?;
let value = Value::string(format!("{arg1}{arg2}"));
Ok(value)
}
DefaultFunction::EqualsString => {
let arg1 = args[0].unwrap_string();
let arg2 = args[1].unwrap_string();
let arg1 = args[0].unwrap_string()?;
let arg2 = args[1].unwrap_string()?;
let value = Value::bool(arg1 == arg2);
Ok(value)
}
DefaultFunction::EncodeUtf8 => {
let arg1 = args[0].unwrap_string();
let arg1 = args[0].unwrap_string()?;
let bytes = arg1.as_bytes().to_vec();
@ -797,7 +630,7 @@ impl DefaultFunction {
Ok(value)
}
DefaultFunction::DecodeUtf8 => {
let arg1 = args[0].unwrap_byte_string();
let arg1 = args[0].unwrap_byte_string()?;
let string = String::from_utf8(arg1.clone())?;
@ -806,7 +639,7 @@ impl DefaultFunction {
Ok(value)
}
DefaultFunction::IfThenElse => {
let condition = args[0].unwrap_bool();
let condition = args[0].unwrap_bool()?;
if *condition {
Ok(args[1].clone())
@ -815,35 +648,33 @@ impl DefaultFunction {
}
}
DefaultFunction::ChooseUnit => {
// We don't need to check it here because this is already done in
// expect_type
// let Value::Con(Constant::Unit) = args[0] else {unreachable!()};
args[0].unwrap_unit()?;
Ok(args[1].clone())
}
DefaultFunction::Trace => {
let arg1 = args[0].unwrap_string();
let arg1 = args[0].unwrap_string()?;
logs.push(arg1.clone());
Ok(args[1].clone())
}
DefaultFunction::FstPair => {
let (_, _, first, _) = args[0].unwrap_pair();
let (_, _, first, _) = args[0].unwrap_pair()?;
let value = Value::Con(first.clone());
Ok(value)
}
DefaultFunction::SndPair => {
let (_, _, _, second) = args[0].unwrap_pair();
let (_, _, _, second) = args[0].unwrap_pair()?;
let value = Value::Con(second.clone());
Ok(value)
}
DefaultFunction::ChooseList => {
let (_, list) = args[0].unwrap_list();
let (_, list) = args[0].unwrap_list()?;
if list.is_empty() {
Ok(args[1].clone())
@ -852,8 +683,12 @@ impl DefaultFunction {
}
}
DefaultFunction::MkCons => {
let item = args[0].unwrap_constant();
let (r#type, list) = args[1].unwrap_list();
let item = args[0].unwrap_constant()?;
let (r#type, list) = args[1].unwrap_list()?;
if *r#type != Type::from(item) {
return Err(Error::TypeMismatch(Type::from(item), r#type.clone()));
}
let mut ret = vec![item.clone()];
@ -864,15 +699,10 @@ impl DefaultFunction {
Ok(value)
}
DefaultFunction::HeadList => {
let c @ Value::Con(inner) = &args[0] else {
unreachable!()
};
let Constant::ProtoList(_, list) = inner.as_ref() else {
unreachable!()
};
let (_, list) = args[0].unwrap_list()?;
if list.is_empty() {
Err(Error::EmptyList(c.clone()))
Err(Error::EmptyList(args[0].clone()))
} else {
let value = Value::Con(list[0].clone().into());
@ -880,15 +710,10 @@ impl DefaultFunction {
}
}
DefaultFunction::TailList => {
let c @ Value::Con(inner) = &args[0] else {
unreachable!()
};
let Constant::ProtoList(r#type, list) = inner.as_ref() else {
unreachable!()
};
let (r#type, list) = args[0].unwrap_list()?;
if list.is_empty() {
Err(Error::EmptyList(c.clone()))
Err(Error::EmptyList(args[0].clone()))
} else {
let value = Value::list(r#type.clone(), list[1..].to_vec());
@ -896,27 +721,26 @@ impl DefaultFunction {
}
}
DefaultFunction::NullList => {
let (_, list) = args[0].unwrap_list();
let (_, list) = args[0].unwrap_list()?;
let value = Value::bool(list.is_empty());
Ok(value)
}
DefaultFunction::ChooseData => {
let con = args[0].unwrap_constant();
let con = args[0].unwrap_data()?;
match con {
Constant::Data(PlutusData::Constr(_)) => Ok(args[1].clone()),
Constant::Data(PlutusData::Map(_)) => Ok(args[2].clone()),
Constant::Data(PlutusData::Array(_)) => Ok(args[3].clone()),
Constant::Data(PlutusData::BigInt(_)) => Ok(args[4].clone()),
Constant::Data(PlutusData::BoundedBytes(_)) => Ok(args[5].clone()),
_ => unreachable!(),
PlutusData::Constr(_) => Ok(args[1].clone()),
PlutusData::Map(_) => Ok(args[2].clone()),
PlutusData::Array(_) => Ok(args[3].clone()),
PlutusData::BigInt(_) => Ok(args[4].clone()),
PlutusData::BoundedBytes(_) => Ok(args[5].clone()),
}
}
DefaultFunction::ConstrData => {
let i = args[0].unwrap_integer();
let l = args[1].unwrap_data_list();
let i = args[0].unwrap_integer()?;
let l = args[1].unwrap_data_list()?;
let data_list: Vec<PlutusData> = l
.iter()
@ -939,7 +763,17 @@ impl DefaultFunction {
Ok(value)
}
DefaultFunction::MapData => {
let (_, list) = args[0].unwrap_list();
let (r#type, list) = args[0].unwrap_list()?;
if *r#type != Type::Pair(Rc::new(Type::Data), Rc::new(Type::Data)) {
return Err(Error::TypeMismatch(
Type::List(Rc::new(Type::Pair(
Rc::new(Type::Data),
Rc::new(Type::Data),
))),
r#type.clone(),
));
}
let mut map = Vec::new();
@ -948,12 +782,13 @@ impl DefaultFunction {
unreachable!()
};
match (left.as_ref(), right.as_ref()) {
(Constant::Data(key), Constant::Data(value)) => {
map.push((key.clone(), value.clone()));
}
_ => unreachable!(),
}
let (Constant::Data(key), Constant::Data(value)) =
(left.as_ref(), right.as_ref())
else {
unreachable!()
};
map.push((key.clone(), value.clone()));
}
let value = Value::data(PlutusData::Map(map.into()));
@ -961,7 +796,7 @@ impl DefaultFunction {
Ok(value)
}
DefaultFunction::ListData => {
let (_, list) = args[0].unwrap_list();
let list = args[0].unwrap_data_list()?;
let data_list: Vec<PlutusData> = list
.iter()
@ -976,14 +811,14 @@ impl DefaultFunction {
Ok(value)
}
DefaultFunction::IData => {
let i = args[0].unwrap_integer();
let i = args[0].unwrap_integer()?;
let value = Value::data(PlutusData::BigInt(to_pallas_bigint(i)));
Ok(value)
}
DefaultFunction::BData => {
let b = args[0].unwrap_byte_string();
let b = args[0].unwrap_byte_string()?;
let value = Value::data(PlutusData::BoundedBytes(b.clone().try_into().unwrap()));
@ -1022,10 +857,7 @@ impl DefaultFunction {
Ok(value)
}
v => Err(Error::DeserialisationError(
"UnConstrData".to_string(),
v.clone(),
)),
v => Err(Error::NotAConstant(v.clone())),
},
DefaultFunction::UnMapData => match &args[0] {
v @ Value::Con(inner) => {
@ -1055,10 +887,7 @@ impl DefaultFunction {
Ok(value)
}
v => Err(Error::DeserialisationError(
"UnMapData".to_string(),
v.clone(),
)),
v => Err(Error::NotAConstant(v.clone())),
},
DefaultFunction::UnListData => match &args[0] {
v @ Value::Con(inner) => {
@ -1079,10 +908,7 @@ impl DefaultFunction {
Ok(value)
}
v => Err(Error::DeserialisationError(
"UnListData".to_string(),
v.clone(),
)),
v => Err(Error::NotAConstant(v.clone())),
},
DefaultFunction::UnIData => match &args[0] {
v @ Value::Con(inner) => {
@ -1097,10 +923,7 @@ impl DefaultFunction {
Ok(value)
}
v => Err(Error::DeserialisationError(
"UnIData".to_string(),
v.clone(),
)),
v => Err(Error::NotAConstant(v.clone())),
},
DefaultFunction::UnBData => match &args[0] {
v @ Value::Con(inner) => {
@ -1115,34 +938,18 @@ impl DefaultFunction {
Ok(value)
}
v => Err(Error::DeserialisationError(
"UnBData".to_string(),
v.clone(),
)),
v => Err(Error::NotAConstant(v.clone())),
},
DefaultFunction::EqualsData => {
let (Value::Con(inner1), Value::Con(inner2)) = (&args[0], &args[1]) else {
unreachable!()
};
let Constant::Data(d1) = inner1.as_ref() else {
unreachable!()
};
let Constant::Data(d2) = inner2.as_ref() else {
unreachable!()
};
let d1 = args[0].unwrap_data()?;
let d2 = args[1].unwrap_data()?;
let value = Value::bool(d1.eq(d2));
Ok(value)
}
DefaultFunction::SerialiseData => {
let Value::Con(inner) = &args[0] else {
unreachable!()
};
let Constant::Data(d) = inner.as_ref() else {
unreachable!()
};
let d = args[0].unwrap_data()?;
let serialized_data = plutus_data_to_bytes(d).unwrap();
@ -1151,16 +958,8 @@ impl DefaultFunction {
Ok(value)
}
DefaultFunction::MkPairData => {
let (Value::Con(inner1), Value::Con(inner2)) = (&args[0], &args[1]) else {
unreachable!()
};
let Constant::Data(d1) = inner1.as_ref() else {
unreachable!()
};
let Constant::Data(d2) = inner2.as_ref() else {
unreachable!()
};
let d1 = args[0].unwrap_data()?;
let d2 = args[1].unwrap_data()?;
let constant = Constant::ProtoPair(
Type::Data,
@ -1174,11 +973,15 @@ impl DefaultFunction {
Ok(value)
}
DefaultFunction::MkNilData => {
args[0].unwrap_unit()?;
let value = Value::list(Type::Data, vec![]);
Ok(value)
}
DefaultFunction::MkNilPairData => {
args[0].unwrap_unit()?;
let constant = Constant::ProtoList(
Type::Pair(Rc::new(Type::Data), Rc::new(Type::Data)),
vec![],
@ -1190,8 +993,8 @@ impl DefaultFunction {
}
DefaultFunction::Bls12_381_G1_Add => {
let arg1 = args[0].unwrap_bls12_381_g1_element();
let arg2 = args[1].unwrap_bls12_381_g1_element();
let arg1 = args[0].unwrap_bls12_381_g1_element()?;
let arg2 = args[1].unwrap_bls12_381_g1_element()?;
let mut out = blst::blst_p1::default();
@ -1208,7 +1011,7 @@ impl DefaultFunction {
Ok(Value::Con(constant.into()))
}
DefaultFunction::Bls12_381_G1_Neg => {
let arg1 = args[0].unwrap_bls12_381_g1_element();
let arg1 = args[0].unwrap_bls12_381_g1_element()?;
let mut out = *arg1;
@ -1225,8 +1028,8 @@ impl DefaultFunction {
Ok(Value::Con(constant.into()))
}
DefaultFunction::Bls12_381_G1_ScalarMul => {
let arg1 = args[0].unwrap_integer();
let arg2 = args[1].unwrap_bls12_381_g1_element();
let arg1 = args[0].unwrap_integer()?;
let arg2 = args[1].unwrap_bls12_381_g1_element()?;
let size_scalar = size_of::<blst::blst_scalar>();
@ -1266,8 +1069,8 @@ impl DefaultFunction {
Ok(Value::Con(constant.into()))
}
DefaultFunction::Bls12_381_G1_Equal => {
let arg1 = args[0].unwrap_bls12_381_g1_element();
let arg2 = args[1].unwrap_bls12_381_g1_element();
let arg1 = args[0].unwrap_bls12_381_g1_element()?;
let arg2 = args[1].unwrap_bls12_381_g1_element()?;
let is_equal = unsafe { blst::blst_p1_is_equal(arg1, arg2) };
@ -1276,7 +1079,7 @@ impl DefaultFunction {
Ok(Value::Con(constant.into()))
}
DefaultFunction::Bls12_381_G1_Compress => {
let arg1 = args[0].unwrap_bls12_381_g1_element();
let arg1 = args[0].unwrap_bls12_381_g1_element()?;
let out = arg1.compress();
@ -1285,7 +1088,7 @@ impl DefaultFunction {
Ok(Value::Con(constant.into()))
}
DefaultFunction::Bls12_381_G1_Uncompress => {
let arg1 = args[0].unwrap_byte_string();
let arg1 = args[0].unwrap_byte_string()?;
let out = blst::blst_p1::uncompress(arg1)?;
@ -1294,8 +1097,8 @@ impl DefaultFunction {
Ok(Value::Con(constant.into()))
}
DefaultFunction::Bls12_381_G1_HashToGroup => {
let arg1 = args[0].unwrap_byte_string();
let arg2 = args[1].unwrap_byte_string();
let arg1 = args[0].unwrap_byte_string()?;
let arg2 = args[1].unwrap_byte_string()?;
if arg2.len() > 255 {
return Err(Error::HashToCurveDstTooBig);
@ -1321,8 +1124,8 @@ impl DefaultFunction {
Ok(Value::Con(constant.into()))
}
DefaultFunction::Bls12_381_G2_Add => {
let arg1 = args[0].unwrap_bls12_381_g2_element();
let arg2 = args[1].unwrap_bls12_381_g2_element();
let arg1 = args[0].unwrap_bls12_381_g2_element()?;
let arg2 = args[1].unwrap_bls12_381_g2_element()?;
let mut out = blst::blst_p2::default();
@ -1339,7 +1142,7 @@ impl DefaultFunction {
Ok(Value::Con(constant.into()))
}
DefaultFunction::Bls12_381_G2_Neg => {
let arg1 = args[0].unwrap_bls12_381_g2_element();
let arg1 = args[0].unwrap_bls12_381_g2_element()?;
let mut out = *arg1;
@ -1356,8 +1159,8 @@ impl DefaultFunction {
Ok(Value::Con(constant.into()))
}
DefaultFunction::Bls12_381_G2_ScalarMul => {
let arg1 = args[0].unwrap_integer();
let arg2 = args[1].unwrap_bls12_381_g2_element();
let arg1 = args[0].unwrap_integer()?;
let arg2 = args[1].unwrap_bls12_381_g2_element()?;
let size_scalar = size_of::<blst::blst_scalar>();
@ -1397,8 +1200,8 @@ impl DefaultFunction {
Ok(Value::Con(constant.into()))
}
DefaultFunction::Bls12_381_G2_Equal => {
let arg1 = args[0].unwrap_bls12_381_g2_element();
let arg2 = args[1].unwrap_bls12_381_g2_element();
let arg1 = args[0].unwrap_bls12_381_g2_element()?;
let arg2 = args[1].unwrap_bls12_381_g2_element()?;
let is_equal = unsafe { blst::blst_p2_is_equal(arg1, arg2) };
@ -1407,7 +1210,7 @@ impl DefaultFunction {
Ok(Value::Con(constant.into()))
}
DefaultFunction::Bls12_381_G2_Compress => {
let arg1 = args[0].unwrap_bls12_381_g2_element();
let arg1 = args[0].unwrap_bls12_381_g2_element()?;
let out = arg1.compress();
@ -1416,7 +1219,7 @@ impl DefaultFunction {
Ok(Value::Con(constant.into()))
}
DefaultFunction::Bls12_381_G2_Uncompress => {
let arg1 = args[0].unwrap_byte_string();
let arg1 = args[0].unwrap_byte_string()?;
let out = blst::blst_p2::uncompress(arg1)?;
@ -1425,8 +1228,8 @@ impl DefaultFunction {
Ok(Value::Con(constant.into()))
}
DefaultFunction::Bls12_381_G2_HashToGroup => {
let arg1 = args[0].unwrap_byte_string();
let arg2 = args[1].unwrap_byte_string();
let arg1 = args[0].unwrap_byte_string()?;
let arg2 = args[1].unwrap_byte_string()?;
if arg2.len() > 255 {
return Err(Error::HashToCurveDstTooBig);
@ -1452,8 +1255,8 @@ impl DefaultFunction {
Ok(Value::Con(constant.into()))
}
DefaultFunction::Bls12_381_MillerLoop => {
let arg1 = args[0].unwrap_bls12_381_g1_element();
let arg2 = args[1].unwrap_bls12_381_g2_element();
let arg1 = args[0].unwrap_bls12_381_g1_element()?;
let arg2 = args[1].unwrap_bls12_381_g2_element()?;
let mut out = blst::blst_fp12::default();
@ -1472,8 +1275,8 @@ impl DefaultFunction {
Ok(Value::Con(constant.into()))
}
DefaultFunction::Bls12_381_MulMlResult => {
let arg1 = args[0].unwrap_bls12_381_ml_result();
let arg2 = args[1].unwrap_bls12_381_ml_result();
let arg1 = args[0].unwrap_bls12_381_ml_result()?;
let arg2 = args[1].unwrap_bls12_381_ml_result()?;
let mut out = blst::blst_fp12::default();
@ -1486,8 +1289,8 @@ impl DefaultFunction {
Ok(Value::Con(constant.into()))
}
DefaultFunction::Bls12_381_FinalVerify => {
let arg1 = args[0].unwrap_bls12_381_ml_result();
let arg2 = args[1].unwrap_bls12_381_ml_result();
let arg1 = args[0].unwrap_bls12_381_ml_result()?;
let arg2 = args[1].unwrap_bls12_381_ml_result()?;
let verified = unsafe { blst::blst_fp12_finalverify(arg1, arg2) };

View File

@ -69,125 +69,138 @@ impl Value {
Value::Con(constant.into())
}
pub(super) fn unwrap_integer(&self) -> &BigInt {
let Value::Con(inner) = self else {
unreachable!()
};
let Constant::Integer(integer) = inner.as_ref() else {
unreachable!()
pub(super) fn unwrap_integer(&self) -> Result<&BigInt, Error> {
let inner = self.unwrap_constant()?;
let Constant::Integer(integer) = inner else {
return Err(Error::TypeMismatch(Type::Integer, inner.into()));
};
integer
Ok(integer)
}
pub(super) fn unwrap_byte_string(&self) -> &Vec<u8> {
let Value::Con(inner) = self else {
unreachable!()
};
let Constant::ByteString(byte_string) = inner.as_ref() else {
unreachable!()
pub(super) fn unwrap_byte_string(&self) -> Result<&Vec<u8>, Error> {
let inner = self.unwrap_constant()?;
let Constant::ByteString(byte_string) = inner else {
return Err(Error::TypeMismatch(Type::ByteString, inner.into()));
};
byte_string
Ok(byte_string)
}
pub(super) fn unwrap_string(&self) -> &String {
let Value::Con(inner) = self else {
unreachable!()
};
let Constant::String(string) = inner.as_ref() else {
unreachable!()
pub(super) fn unwrap_string(&self) -> Result<&String, Error> {
let inner = self.unwrap_constant()?;
let Constant::String(string) = inner else {
return Err(Error::TypeMismatch(Type::String, inner.into()));
};
string
Ok(string)
}
pub(super) fn unwrap_bool(&self) -> &bool {
let Value::Con(inner) = self else {
unreachable!()
};
let Constant::Bool(condition) = inner.as_ref() else {
unreachable!()
pub(super) fn unwrap_bool(&self) -> Result<&bool, Error> {
let inner = self.unwrap_constant()?;
let Constant::Bool(condition) = inner else {
return Err(Error::TypeMismatch(Type::Bool, inner.into()));
};
condition
Ok(condition)
}
pub(super) fn unwrap_pair(&self) -> (&Type, &Type, &Rc<Constant>, &Rc<Constant>) {
let Value::Con(inner) = self else {
unreachable!()
};
let Constant::ProtoPair(t1, t2, first, second) = inner.as_ref() else {
unreachable!()
#[allow(clippy::type_complexity)]
pub(super) fn unwrap_pair(
&self,
) -> Result<(&Type, &Type, &Rc<Constant>, &Rc<Constant>), Error> {
let inner = self.unwrap_constant()?;
let Constant::ProtoPair(t1, t2, first, second) = inner else {
return Err(Error::PairTypeMismatch(inner.into()));
};
(t1, t2, first, second)
Ok((t1, t2, first, second))
}
pub(super) fn unwrap_list(&self) -> (&Type, &Vec<Constant>) {
let Value::Con(inner) = self else {
unreachable!()
};
let Constant::ProtoList(t, list) = inner.as_ref() else {
unreachable!()
pub(super) fn unwrap_list(&self) -> Result<(&Type, &Vec<Constant>), Error> {
let inner = self.unwrap_constant()?;
let Constant::ProtoList(t, list) = inner else {
return Err(Error::ListTypeMismatch(inner.into()));
};
(t, list)
Ok((t, list))
}
pub(super) fn unwrap_constant(&self) -> &Constant {
pub(super) fn unwrap_data(&self) -> Result<&PlutusData, Error> {
let inner = self.unwrap_constant()?;
let Constant::Data(data) = inner else {
return Err(Error::TypeMismatch(Type::Data, inner.into()));
};
Ok(data)
}
pub(super) fn unwrap_unit(&self) -> Result<(), Error> {
let inner = self.unwrap_constant()?;
let Constant::Unit = inner else {
return Err(Error::TypeMismatch(Type::Unit, inner.into()));
};
Ok(())
}
pub(super) fn unwrap_constant(&self) -> Result<&Constant, Error> {
let Value::Con(item) = self else {
unreachable!()
return Err(Error::NotAConstant(self.clone()));
};
item.as_ref()
Ok(item.as_ref())
}
pub(super) fn unwrap_data_list(&self) -> &Vec<Constant> {
let Value::Con(inner) = self else {
unreachable!()
};
let Constant::ProtoList(Type::Data, list) = inner.as_ref() else {
unreachable!()
pub(super) fn unwrap_data_list(&self) -> Result<&Vec<Constant>, Error> {
let inner = self.unwrap_constant()?;
let Constant::ProtoList(Type::Data, list) = inner else {
return Err(Error::TypeMismatch(
Type::List(Type::Data.into()),
inner.into(),
));
};
list
Ok(list)
}
pub(super) fn unwrap_bls12_381_g1_element(&self) -> &blst::blst_p1 {
let Value::Con(inner) = self else {
unreachable!()
pub(super) fn unwrap_bls12_381_g1_element(&self) -> Result<&blst::blst_p1, Error> {
let inner = self.unwrap_constant()?;
let Constant::Bls12_381G1Element(element) = inner else {
return Err(Error::TypeMismatch(Type::Bls12_381G1Element, inner.into()));
};
let Constant::Bls12_381G1Element(element) = inner.as_ref() else {
unreachable!()
};
element
Ok(element)
}
pub(super) fn unwrap_bls12_381_g2_element(&self) -> &blst::blst_p2 {
let Value::Con(inner) = self else {
unreachable!()
pub(super) fn unwrap_bls12_381_g2_element(&self) -> Result<&blst::blst_p2, Error> {
let inner = self.unwrap_constant()?;
let Constant::Bls12_381G2Element(element) = inner else {
return Err(Error::TypeMismatch(Type::Bls12_381G2Element, inner.into()));
};
let Constant::Bls12_381G2Element(element) = inner.as_ref() else {
unreachable!()
};
element
Ok(element)
}
pub(super) fn unwrap_bls12_381_ml_result(&self) -> &blst::blst_fp12 {
let Value::Con(inner) = self else {
unreachable!()
pub(super) fn unwrap_bls12_381_ml_result(&self) -> Result<&blst::blst_fp12, Error> {
let inner = self.unwrap_constant()?;
let Constant::Bls12_381MlResult(element) = inner else {
return Err(Error::TypeMismatch(Type::Bls12_381MlResult, inner.into()));
};
let Constant::Bls12_381MlResult(element) = inner.as_ref() else {
unreachable!()
};
element
Ok(element)
}
pub fn is_integer(&self) -> bool {

View File

@ -145,7 +145,7 @@ peg::parser! {
}
rule case(interner: &mut Interner) -> Term<Name>
= "(" _* "case" _+ constr:term(interner) _* branches:(t:term(interner) _* { t })+ _* ")" {
= "(" _* "case" _+ constr:term(interner) _* branches:(t:term(interner) _* { t })* _* ")" {
Term::Case { constr: constr.into(), branches }
}

View File

@ -0,0 +1,7 @@
(program
1.0.0
[
[ (force (builtin ifThenElse)) (con string "11 <= 22") ]
(con string "\172(11 <= 22)")
]
)

View File

@ -0,0 +1,4 @@
(program 1.0.0 [
[ (force (builtin ifThenElse)) (con string "11 <= 22") ]
(con string "\172(11 <= 22)")
])

View File

@ -0,0 +1,4 @@
-- select first branch
(program 1.1.0
(case (constr 0 (con integer 0)) (lam x (con integer 1)) (lam x (con integer 2)))
)

View File

@ -0,0 +1 @@
(program 1.1.0 (con integer 1))

View File

@ -0,0 +1,4 @@
-- select second branch
(program 1.1.0
(case (constr 1 (con integer 0)) (lam x (con integer 1)) (lam x (con integer 2)))
)

View File

@ -0,0 +1 @@
(program 1.1.0 (con integer 2))

View File

@ -0,0 +1,4 @@
-- select first branch and do computation with the args
(program 1.1.0
(case (constr 0 (con integer 3) (con integer 2)) (lam x (lam y [(builtin addInteger) x y])) (lam x (lam y [(builtin subtractInteger) x y])))
)

View File

@ -0,0 +1 @@
(program 1.1.0 (con integer 5))

View File

@ -0,0 +1,4 @@
-- select second branch and do computation with the args
(program 1.1.0
(case (constr 1 (con integer 3) (con integer 2)) (lam x (lam y [(builtin addInteger) x y])) (lam x (lam y [(builtin subtractInteger) x y])))
)

View File

@ -0,0 +1 @@
(program 1.1.0 (con integer 1))

View File

@ -0,0 +1,4 @@
-- case of non-constr
(program 1.1.0
(case (con integer 1) (lam x x) (lam x x))
)

View File

@ -0,0 +1 @@
evaluation failure

View File

@ -1,4 +0,0 @@
-- case can't be used before 1.1.0
(program 1.0.0
(case (con integer 1))
)

View File

@ -0,0 +1,4 @@
-- nullary case
(program 1.1.0
(case (constr 0) (con integer 1) (con integer 2))
)

View File

@ -0,0 +1 @@
(program 1.1.0 (con integer 1))

View File

@ -0,0 +1,4 @@
-- empty case, aka -XEmptyCase
(program 1.1.0
(case (constr 0))
)

View File

@ -0,0 +1 @@
evaluation failure

View File

@ -0,0 +1,4 @@
-- empty constr
(program 1.1.0
(constr 0 )
)

View File

@ -0,0 +1 @@
(program 1.1.0 (constr 0))

View File

@ -0,0 +1,4 @@
-- constr with an argument
(program 1.1.0
(constr 0 (con integer 1))
)

View File

@ -0,0 +1 @@
(program 1.1.0 (constr 0 (con integer 1)))

View File

@ -0,0 +1,2 @@
-- ill-typed but does not fail at runtime because the builtin application is not saturated.
(program 1.0.0 [(builtin addInteger) (con unit ())])

View File

@ -0,0 +1 @@
(program 1.0.0 [ (builtin addInteger) (con unit ()) ])

View File

@ -119,12 +119,13 @@ fn check_mint_and_outputs(
when minted_assets is {
[] -> True
[(minted_asset_name, quantity), ..rest_assets] -> {
expect True =
expect
list.any(
expected_assets,
fn(expected_asset) { expected_asset == minted_asset_name },
)
expect True =
expect
list.any(
outputs,
fn(output) {
@ -132,6 +133,7 @@ fn check_mint_and_outputs(
datum == InlineDatum(minted_asset_name) && address.payment_credential == validator_cred
},
)
quantity == 1 && check_mint_and_outputs(
rest_assets,
outputs,