Merge pull request #991 from aiken-lang/pattern-match-bytearrays

Allow pattern-matching on bytearrays
This commit is contained in:
Matthias Benkort 2024-08-03 14:09:12 +02:00 committed by GitHub
commit 09c065d332
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 605 additions and 52 deletions

View File

@ -5,6 +5,7 @@
### Added
- **aiken-lang**: add support for `mk_cons` and `mk_pair_data` builtins. See [#964](https://github.com/aiken-lang/aiken/issues/964). @KtorZ
- **aiken-lang**: pattern-matching on bytearrays is now available. See [#989](https://github.com/aiken-lang/aiken/issues/989). @KtorZ
### Changed

View File

@ -1262,6 +1262,12 @@ pub enum Pattern<Constructor, Type> {
base: Base,
},
ByteArray {
location: Span,
value: Vec<u8>,
preferred_format: ByteArrayFormatPreference,
},
/// The creation of a variable.
/// e.g. `expect [this_is_a_var, .._] = x`
/// e.g. `let foo = 42`
@ -1330,6 +1336,7 @@ impl<A, B> Pattern<A, B> {
| Pattern::Discard { location, .. }
| Pattern::Tuple { location, .. }
| Pattern::Pair { location, .. }
| Pattern::ByteArray { location, .. }
| Pattern::Constructor { location, .. } => *location,
}
}
@ -1383,6 +1390,7 @@ impl TypedPattern {
Pattern::Int { .. }
| Pattern::Var { .. }
| Pattern::Assign { .. }
| Pattern::ByteArray { .. }
| Pattern::Discard { .. } => Some(Located::Pattern(self, value.clone())),
Pattern::List { elements, .. }
@ -1438,6 +1446,7 @@ impl TypedPattern {
pub fn tipo(&self, value: &TypedExpr) -> Option<Rc<Type>> {
match self {
Pattern::Int { .. } => Some(builtins::int()),
Pattern::ByteArray { .. } => Some(builtins::byte_array()),
Pattern::Constructor { tipo, .. } => Some(tipo.clone()),
Pattern::Var { .. } | Pattern::Assign { .. } | Pattern::Discard { .. } => {
Some(value.tipo())

View File

@ -1877,6 +1877,12 @@ impl<'comments> Formatter<'comments> {
let doc = match pattern {
Pattern::Int { value, base, .. } => self.int(value, base),
Pattern::ByteArray {
value,
preferred_format,
..
} => self.bytearray(value, None, preferred_format),
Pattern::Var { name, .. } => name.to_doc(),
Pattern::Assign { name, pattern, .. } => {

View File

@ -17,7 +17,7 @@ use crate::{
Span, TraceLevel, Tracing, TypedArg, TypedClause, TypedDataType, TypedFunction,
TypedPattern, TypedValidator, UnOp,
},
builtins::{bool, data, int, list, void, PRELUDE},
builtins::{bool, byte_array, data, int, list, void, PRELUDE},
expr::TypedExpr,
gen_uplc::{
air::ExpectLevel,
@ -915,26 +915,30 @@ impl<'a> CodeGenerator<'a> {
value: expected_int,
location,
..
} => {
assert!(props.kind.is_expect());
let name = format!(
} => AirTree::assign_literal_pattern(
format!(
"__expected_by_{}_span_{}_{}",
expected_int, location.start, location.end
);
),
AirTree::int(expected_int),
value,
int(),
props,
then,
),
let expect = AirTree::binop(
BinOp::Eq,
bool(),
AirTree::int(expected_int),
AirTree::local_var(&name, int()),
int(),
);
let expr = AirTree::let_assignment(name, value, expect);
AirTree::assert_bool(true, expr, then, props.otherwise.clone())
}
Pattern::ByteArray {
location,
value: expected_bytes,
..
} => AirTree::assign_literal_pattern(
format!("__expected_bytes_span_{}_{}", location.start, location.end),
AirTree::byte_array(expected_bytes.clone()),
value,
byte_array(),
props,
then,
),
Pattern::Var { name, .. } => {
if props.full_check {
@ -2321,6 +2325,10 @@ impl<'a> CodeGenerator<'a> {
assert!(!props.final_clause);
(AirTree::int(value), then)
}
Pattern::ByteArray { value, .. } => {
assert!(!props.final_clause);
(AirTree::byte_array(value.clone()), then)
}
Pattern::Var { name, .. } => (
AirTree::void(),
AirTree::let_assignment(
@ -2839,6 +2847,15 @@ impl<'a> CodeGenerator<'a> {
then,
)
}
Pattern::ByteArray { value, .. } => {
props.complex_clause = true;
AirTree::clause_guard(
&props.original_subject_name,
AirTree::byte_array(value.clone()),
byte_array(),
then,
)
}
Pattern::Var { name, .. } => AirTree::let_assignment(
name,
AirTree::local_var(&props.clause_var_name, subject_tipo.clone()),

View File

@ -615,7 +615,7 @@ pub fn pattern_has_conditions(
data_types: &IndexMap<&DataTypeKey, &TypedDataType>,
) -> bool {
match pattern {
Pattern::List { .. } | Pattern::Int { .. } => true,
Pattern::List { .. } | Pattern::Int { .. } | Pattern::ByteArray { .. } => true,
Pattern::Tuple { elems, .. } => elems
.iter()
.any(|elem| pattern_has_conditions(elem, data_types)),

View File

@ -2,6 +2,7 @@ use super::air::{Air, ExpectLevel};
use crate::{
ast::{BinOp, Curve, Span, UnOp},
builtins::{bool, byte_array, data, int, list, string, void},
gen_uplc::AssignmentProperties,
tipo::{Type, ValueConstructor, ValueConstructorVariant},
};
use indexmap::IndexSet;
@ -588,6 +589,29 @@ impl AirTree {
}
}
pub fn assign_literal_pattern(
name: String,
pattern: AirTree,
rhs: AirTree,
tipo: Rc<Type>,
props: AssignmentProperties,
then: AirTree,
) -> AirTree {
assert!(props.kind.is_expect());
let expect = AirTree::binop(
BinOp::Eq,
bool(),
pattern,
AirTree::local_var(&name, tipo.clone()),
tipo,
);
let expr = AirTree::let_assignment(name, rhs, expect);
AirTree::assert_bool(true, expr, then, props.otherwise.clone())
}
pub fn cast_from_data(
value: AirTree,
tipo: Rc<Type>,

View File

@ -148,6 +148,26 @@ impl ParseError {
label: None,
}
}
pub fn match_on_curve(span: Span) -> Self {
Self {
kind: ErrorKind::PatternMatchOnCurvePoint,
span,
while_parsing: None,
expected: HashSet::new(),
label: Some("cannot pattern-match on curve point"),
}
}
pub fn match_string(span: Span) -> Self {
Self {
kind: ErrorKind::PatternMatchOnString,
span,
while_parsing: None,
expected: HashSet::new(),
label: Some("cannot pattern-match on string"),
}
}
}
impl PartialEq for ParseError {
@ -260,6 +280,18 @@ pub enum ErrorKind {
"#
}))]
DeprecatedWhenClause,
#[error("I choked on a curve point in a bytearray pattern.")]
#[diagnostic(help(
"You can pattern-match on bytearrays just fine, but not on G1 nor G2 elements. Use if/else with an equality if you have to compare those."
))]
PatternMatchOnCurvePoint,
#[error("I refuse to cooperate and match a utf-8 string.")]
#[diagnostic(help(
"You can pattern-match on bytearrays but not on strings. Note that I can parse utf-8 encoded bytearrays just fine, so you probably want to drop the extra '@' and only manipulate bytearrays wherever you need to. On-chain, strings shall be avoided as much as possible."
))]
PatternMatchOnString,
}
fn fmt_curve_type(curve: &CurveType) -> String {

View File

@ -0,0 +1,63 @@
use crate::{
ast::UntypedPattern,
parser::{error::ParseError, literal, token::Token},
};
use chumsky::prelude::*;
pub fn parser() -> impl Parser<Token, UntypedPattern, Error = ParseError> {
literal::bytearray(|value, preferred_format, curve, location, emit| {
if curve.is_some() {
emit(ParseError::match_on_curve(location));
}
UntypedPattern::ByteArray {
location,
value,
preferred_format,
}
})
}
#[cfg(test)]
mod tests {
use crate::assert_expr;
#[test]
fn pattern_bytearray() {
assert_expr!(
r#"
when foo is {
#"00abcd" -> True
"Aiken, rocks!" -> True
#[1, 2, 3, 4] -> True
#[0x00, 0xab, 0xcd] -> True
_ -> False
}
"#
);
}
#[test]
fn pattern_bytearray_g1_element() {
assert_expr!(
r#"
when foo is {
#<Bls12_381, G1>"950dfd33da2682260c76038dfb8bad6e84ae9d599a3c151815945ac1e6ef6b1027cd917f3907479d20d636ce437a41f5" -> False
_ -> True
}
"#
);
}
#[test]
fn pattern_bytearray_g2_element() {
assert_expr!(
r#"
when foo is {
#<Bls12_381, G2>"b0629fa1158c2d23a10413fe91d381a84d25e31d041cd0377d25828498fd02011b35893938ced97535395e4815201e67108bcd4665e0db25d602d76fa791fab706c54abf5e1a9e44b4ac1e6badf3d2ac0328f5e30be341677c8bac5dda7682f1" -> False
_ -> True
}
"#
);
}
}

View File

@ -1,10 +1,12 @@
use chumsky::prelude::*;
mod bytearray;
mod constructor;
mod discard;
mod int;
mod list;
mod pair;
mod string;
mod tuple;
mod var;
@ -12,11 +14,13 @@ use crate::{
ast::UntypedPattern,
parser::{error::ParseError, token::Token},
};
pub use bytearray::parser as bytearray;
pub use constructor::parser as constructor;
pub use discard::parser as discard;
pub use int::parser as int;
pub use list::parser as list;
pub use pair::parser as pair;
pub use string::parser as string;
pub use tuple::parser as tuple;
pub use var::parser as var;
@ -28,8 +32,10 @@ pub fn parser() -> impl Parser<Token, UntypedPattern, Error = ParseError> {
constructor(pattern.clone()),
discard(),
int(),
bytearray(),
tuple(pattern.clone()),
list(pattern),
string(),
))
.then(
just(Token::As)

View File

@ -0,0 +1,115 @@
---
source: crates/aiken-lang/src/parser/pattern/bytearray.rs
description: "Code:\n\nwhen foo is {\n #\"00abcd\" -> True\n \"Aiken, rocks!\" -> True\n #[1, 2, 3, 4] -> True\n #[0x00, 0xab, 0xcd] -> True\n _ -> False\n}\n"
---
When {
location: 0..138,
subject: Var {
location: 5..8,
name: "foo",
},
clauses: [
UntypedClause {
location: 18..35,
patterns: [
ByteArray {
location: 18..27,
value: [
0,
171,
205,
],
preferred_format: HexadecimalString,
},
],
then: Var {
location: 31..35,
name: "True",
},
},
UntypedClause {
location: 40..63,
patterns: [
ByteArray {
location: 40..55,
value: [
65,
105,
107,
101,
110,
44,
32,
114,
111,
99,
107,
115,
33,
],
preferred_format: Utf8String,
},
],
then: Var {
location: 59..63,
name: "True",
},
},
UntypedClause {
location: 68..89,
patterns: [
ByteArray {
location: 68..81,
value: [
1,
2,
3,
4,
],
preferred_format: ArrayOfBytes(
Decimal {
numeric_underscore: false,
},
),
},
],
then: Var {
location: 85..89,
name: "True",
},
},
UntypedClause {
location: 94..121,
patterns: [
ByteArray {
location: 94..113,
value: [
0,
171,
205,
],
preferred_format: ArrayOfBytes(
Hexadecimal,
),
},
],
then: Var {
location: 117..121,
name: "True",
},
},
UntypedClause {
location: 126..136,
patterns: [
Discard {
name: "_",
location: 126..127,
},
],
then: Var {
location: 131..136,
name: "False",
},
},
],
}

View File

@ -0,0 +1,15 @@
---
source: crates/aiken-lang/src/parser/pattern/bytearray.rs
description: "Invalid code (parse error):\n\nwhen foo is {\n #<Bls12_381, G1>\"950dfd33da2682260c76038dfb8bad6e84ae9d599a3c151815945ac1e6ef6b1027cd917f3907479d20d636ce437a41f5\" -> False\n _ -> True\n}\n"
---
[
ParseError {
kind: PatternMatchOnCurvePoint,
span: 18..132,
while_parsing: None,
expected: {},
label: Some(
"cannot pattern-match on curve point",
),
},
]

View File

@ -0,0 +1,15 @@
---
source: crates/aiken-lang/src/parser/pattern/bytearray.rs
description: "Invalid code (parse error):\n\nwhen foo is {\n #<Bls12_381, G2>\"b0629fa1158c2d23a10413fe91d381a84d25e31d041cd0377d25828498fd02011b35893938ced97535395e4815201e67108bcd4665e0db25d602d76fa791fab706c54abf5e1a9e44b4ac1e6badf3d2ac0328f5e30be341677c8bac5dda7682f1\" -> False\n _ -> True\n}\n"
---
[
ParseError {
kind: PatternMatchOnCurvePoint,
span: 18..228,
while_parsing: None,
expected: {},
label: Some(
"cannot pattern-match on curve point",
),
},
]

View File

@ -0,0 +1,15 @@
---
source: crates/aiken-lang/src/parser/pattern/string.rs
description: "Invalid code (parse error):\n\nwhen foo is {\n @\"foo\" -> True\n}\n"
---
[
ParseError {
kind: PatternMatchOnString,
span: 16..22,
while_parsing: None,
expected: {},
label: Some(
"cannot pattern-match on string",
),
},
]

View File

@ -0,0 +1,33 @@
use crate::{
ast::{ByteArrayFormatPreference, UntypedPattern},
parser::{error::ParseError, literal, token::Token},
};
use chumsky::prelude::*;
pub fn parser() -> impl Parser<Token, UntypedPattern, Error = ParseError> {
literal::string().validate(|_, location, emit| {
emit(ParseError::match_string(location));
UntypedPattern::ByteArray {
location,
value: Vec::new(),
preferred_format: ByteArrayFormatPreference::Utf8String,
}
})
}
#[cfg(test)]
mod tests {
use crate::assert_expr;
#[test]
fn pattern_string() {
assert_expr!(
r#"
when foo is {
@"foo" -> True
}
"#
);
}
}

View File

@ -2883,3 +2883,74 @@ fn side_effects() {
unreachable!();
}
}
#[test]
fn pattern_bytearray() {
let source_code = r#"
pub fn main(foo: ByteArray) {
when foo is {
#[1, 2, 3] -> True
#"00ff" -> True
"Aiken, rocks!" -> True
_ -> False
}
}
"#;
let result = check(parse(source_code));
assert!(result.is_ok());
let (warnings, _) = result.unwrap();
assert!(warnings.is_empty(), "no warnings: {warnings:#?}");
}
#[test]
fn pattern_bytearray_not_unify_clause_list() {
let source_code = r#"
pub fn main(foo: ByteArray) {
when foo is {
[1, 2, 3] -> True
_ -> False
}
}
"#;
assert!(matches!(
check(parse(source_code)),
Err((_, Error::CouldNotUnify { .. }))
))
}
#[test]
fn pattern_bytearray_not_unify_clause_int() {
let source_code = r#"
pub fn main(foo: ByteArray) {
when foo is {
42 -> True
_ -> False
}
}
"#;
assert!(matches!(
check(parse(source_code)),
Err((_, Error::CouldNotUnify { .. }))
))
}
#[test]
fn pattern_bytearray_not_unify_subject() {
let source_code = r#"
pub fn main(foo: String) {
when foo is {
"42" -> True
_ -> False
}
}
"#;
assert!(matches!(
check(parse(source_code)),
Err((_, Error::CouldNotUnify { .. }))
))
}

View File

@ -1000,3 +1000,20 @@ fn format_variadic_trace() {
"#
);
}
#[test]
fn format_pattern_bytearray() {
assert_format!(
r#"
fn main(foo) {
when foo is {
"Aiken, rocks!" -> True
#"00abcd" -> True
#[1, 2, 3, 4] -> True
#[0x00, 0xab, 0xcd] -> True
_ -> False
}
}
"#
);
}

View File

@ -0,0 +1,13 @@
---
source: crates/aiken-lang/src/tests/format.rs
description: "Code:\n\nfn main(foo) {\n when foo is {\n \"Aiken, rocks!\" -> True\n #\"00abcd\" -> True\n #[1, 2, 3, 4] -> True\n #[0x00, 0xab, 0xcd] -> True\n _ -> False\n }\n}\n"
---
fn main(foo) {
when foo is {
"Aiken, rocks!" -> True
#"00abcd" -> True
#[1, 2, 3, 4] -> True
#[0x00, 0xab, 0xcd] -> True
_ -> False
}
}

View File

@ -381,6 +381,7 @@ pub(crate) enum Pattern {
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum Literal {
Int(String),
ByteArray(Vec<u8>),
}
impl Pattern {
@ -536,6 +537,9 @@ pub(super) fn simplify(
) -> Result<Pattern, Error> {
match value {
ast::Pattern::Int { value, .. } => Ok(Pattern::Literal(Literal::Int(value.clone()))),
ast::Pattern::ByteArray { value, .. } => {
Ok(Pattern::Literal(Literal::ByteArray(value.clone())))
}
ast::Pattern::Assign { pattern, .. } => simplify(environment, pattern.as_ref()),
ast::Pattern::List { elements, tail, .. } => {
let mut p = if let Some(t) = tail {

View File

@ -8,7 +8,7 @@ use super::{
};
use crate::{
ast::{CallArg, Pattern, Span, TypedPattern, UntypedPattern},
builtins::{int, list, pair, tuple},
builtins::{byte_array, int, list, pair, tuple},
};
use itertools::Itertools;
use std::{
@ -199,6 +199,21 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
})
}
Pattern::ByteArray {
location,
value,
preferred_format,
} => {
self.environment
.unify(tipo, byte_array(), location, false)?;
Ok(Pattern::ByteArray {
location,
value,
preferred_format,
})
}
Pattern::List {
location,
elements,

View File

@ -34,7 +34,7 @@ pub fn start() -> Result<(), Error> {
let (connection, io_threads) = Connection::stdio();
// Run the server and wait for the two threads to end (typically by trigger LSP Exit event).
let server_capabilities = serde_json::to_value(&capabilities())?;
let server_capabilities = serde_json::to_value(capabilities())?;
let initialization_params = connection.initialize(server_capabilities)?;
let initialize_params = serde_json::from_value(initialization_params)?;

View File

@ -62,7 +62,7 @@ fn assert_uplc(source_code: &str, expected: Term<Name>, should_fail: bool) {
let expected: Program<DeBruijn> = expected.try_into().unwrap();
assert_eq!(debruijn_program.to_pretty(), expected.to_pretty());
assert!(debruijn_program.to_pretty() == expected.to_pretty());
let mut eval = debruijn_program.eval(ExBudget::default());
@ -6631,3 +6631,42 @@ fn mk_pair_data() {
false,
)
}
#[test]
fn pattern_bytearray() {
let src = r#"
test pattern_bytearray() {
let bytes = "foo"
when bytes is {
"bar" -> False
#[0x66, 0x6f, 0x6f] -> True
_ -> False
}
}
"#;
let snd_clause = Term::equals_bytestring()
.apply(Term::byte_string(vec![0x66, 0x6f, 0x6f]))
.apply(Term::var("__subject"))
.delayed_if_then_else(Term::bool(true), Term::bool(false));
let fst_clause = Term::equals_bytestring()
.apply(Term::byte_string(vec![0x62, 0x61, 0x72]))
.apply(Term::var("__subject"))
.delayed_if_then_else(Term::bool(false), snd_clause);
let when_clause = fst_clause
.lambda("__subject")
.apply(Term::var("__when_var"))
.lambda("__when_var")
.apply(Term::var("bytes"));
let program = when_clause
.lambda("bytes")
.apply(Term::byte_string(vec![0x66, 0x6f, 0x6f]))
// Not sure what this extra lambda is or do?
.lambda("???")
.apply(Term::Error.delay());
assert_uplc(src, program, false)
}

View File

@ -22,19 +22,34 @@ pub struct Args {
uplc: bool,
/// Filter traces to be included in the generated program(s).
/// - user-defined: only consider traces that you've explicitly defined (either through the
/// 'trace' keyword of via the trace-if-false ('?') operator.
/// - compiler-generated: only included internal traces generated by the Aiken compiler, for
/// example in usage of 'expect'.
/// - all: include both user-defined and compiler-generated traces.
///
/// - user-defined:
/// only consider traces that you've explicitly defined
/// either through the 'trace' keyword of via the trace-if-false
/// ('?') operator.
///
/// - compiler-generated:
/// only included internal traces generated by the
/// Aiken compiler, for example in usage of 'expect'.
///
/// - all:
/// include both user-defined and compiler-generated traces.
///
/// [optional] [default: all]
#[clap(short, long, value_parser=filter_traces_parser(), default_missing_value="all", verbatim_doc_comment)]
filter_traces: Option<fn(TraceLevel) -> Tracing>,
/// Choose the verbosity level of traces:
/// - silent: disable traces altogether
/// - compact: only culprit line numbers are shown on failures
/// - verbose: enable full verbose traces as provided by the user or the compiler
///
/// - silent:
/// disable traces altogether
///
/// - compact:
/// only culprit line numbers are shown on failures
///
/// - verbose:
/// enable full verbose traces as provided by the user or the compiler
///
/// [optional]
#[clap(short, long, value_parser=trace_level_parser(), default_value_t=TraceLevel::Silent, verbatim_doc_comment)]
trace_level: TraceLevel,

View File

@ -48,20 +48,35 @@ pub struct Args {
#[clap(short, long)]
exact_match: bool,
/// Filter traces to be considered during testing:
/// - user-defined: only consider traces that you've explicitly defined (either through the
/// 'trace' keyword of via the trace-if-false ('?') operator.
/// - compiler-generated: only included internal traces generated by the Aiken compiler, for
/// example in usage of 'expect'.
/// - all: include both user-defined and compiler-generated traces.
/// Filter traces to be included in the generated program(s).
///
/// - user-defined:
/// only consider traces that you've explicitly defined
/// either through the 'trace' keyword of via the trace-if-false
/// ('?') operator.
///
/// - compiler-generated:
/// only included internal traces generated by the
/// Aiken compiler, for example in usage of 'expect'.
///
/// - all:
/// include both user-defined and compiler-generated traces.
///
/// [optional] [default: all]
#[clap(short, long, value_parser=filter_traces_parser(), default_missing_value="all", verbatim_doc_comment)]
filter_traces: Option<fn(TraceLevel) -> Tracing>,
/// Choose the verbosity level of traces:
/// - silent: disable traces altogether
/// - compact: only culprit line numbers are shown on failures
/// - verbose: enable full verbose traces as provided by the user or the compiler
///
/// - silent:
/// disable traces altogether
///
/// - compact:
/// only culprit line numbers are shown on failures
///
/// - verbose:
/// enable full verbose traces as provided by the user or the compiler
///
/// [optional]
#[clap(short, long, value_parser=trace_level_parser(), default_value_t=TraceLevel::Verbose, verbatim_doc_comment)]
trace_level: TraceLevel,

View File

@ -1,9 +1,7 @@
use std::path::PathBuf;
use super::build::{filter_traces_parser, trace_level_parser};
use aiken_lang::ast::{TraceLevel, Tracing};
use aiken_project::{options::Options, watch::with_project};
use super::build::{filter_traces_parser, trace_level_parser};
use std::path::PathBuf;
#[derive(clap::Args)]
pub struct Args {
@ -18,20 +16,35 @@ pub struct Args {
#[clap(short, long)]
name: String,
/// Filter traces to be considered during testing:
/// - user-defined: only consider traces that you've explicitly defined (either through the
/// 'trace' keyword of via the trace-if-false ('?') operator.
/// - compiler-generated: only included internal traces generated by the Aiken compiler, for
/// example in usage of 'expect'.
/// - all: include both user-defined and compiler-generated traces.
/// Filter traces to be included in the generated program(s).
///
/// - user-defined:
/// only consider traces that you've explicitly defined
/// either through the 'trace' keyword of via the trace-if-false
/// ('?') operator.
///
/// - compiler-generated:
/// only included internal traces generated by the
/// Aiken compiler, for example in usage of 'expect'.
///
/// - all:
/// include both user-defined and compiler-generated traces.
///
/// [optional] [default: all]
#[clap(short, long, value_parser=filter_traces_parser(), default_missing_value="all", verbatim_doc_comment)]
filter_traces: Option<fn(TraceLevel) -> Tracing>,
/// Choose the verbosity level of traces:
/// - silent: disable traces altogether
/// - compact: only culprit line numbers are shown on failures
/// - verbose: enable full verbose traces as provided by the user or the compiler
///
/// - silent:
/// disable traces altogether
///
/// - compact:
/// only culprit line numbers are shown on failures
///
/// - verbose:
/// enable full verbose traces as provided by the user or the compiler
///
/// [optional]
#[clap(short, long, value_parser=trace_level_parser(), default_value_t=TraceLevel::Verbose, verbatim_doc_comment)]
trace_level: TraceLevel,