Add 'diagnostic' to the prelude, as well as companion functions.

This is not fully satisfactory as it pollutes a bit the prelude. Ideally, those functions should only be visible
  and usable by the underlying trace code. But for now, we'll just go with it.
This commit is contained in:
KtorZ 2024-07-18 16:07:31 +02:00
parent 754ed07408
commit beb5ac4643
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
9 changed files with 378 additions and 21 deletions

View File

@ -1,4 +1,5 @@
use crate::{ use crate::{
aiken_fn,
ast::{ ast::{
Annotation, ArgName, CallArg, DataTypeKey, Function, FunctionAccessKey, ModuleKind, Annotation, ArgName, CallArg, DataTypeKey, Function, FunctionAccessKey, ModuleKind,
OnTestFailure, Span, TypedArg, TypedDataType, TypedFunction, UnOp, OnTestFailure, Span, TypedArg, TypedDataType, TypedFunction, UnOp,
@ -268,6 +269,102 @@ pub fn prelude(id_gen: &IdGenerator) -> TypeInfo {
), ),
); );
// enumerate
let enumerate_a = generic_var(id_gen.next());
let enumerate_b = generic_var(id_gen.next());
prelude.values.insert(
"enumerate".to_string(),
ValueConstructor::public(
function(
vec![
list(enumerate_a.clone()),
enumerate_b.clone(),
function(
vec![enumerate_a.clone(), enumerate_b.clone()],
enumerate_b.clone(),
),
function(
vec![enumerate_a.clone(), enumerate_b.clone()],
enumerate_b.clone(),
),
],
enumerate_b,
),
ValueConstructorVariant::ModuleFn {
name: "enumerate".to_string(),
field_map: None,
module: "".to_string(),
arity: 4,
location: Span::empty(),
builtin: None,
},
),
);
// encode_base16
prelude.values.insert(
"encode_base16".to_string(),
ValueConstructor::public(
function(vec![byte_array(), int(), byte_array()], byte_array()),
ValueConstructorVariant::ModuleFn {
name: "encode_base16".to_string(),
field_map: None,
module: "".to_string(),
arity: 3,
location: Span::empty(),
builtin: None,
},
),
);
// from_int
prelude.values.insert(
"from_int".to_string(),
ValueConstructor::public(
function(vec![int(), byte_array()], byte_array()),
ValueConstructorVariant::ModuleFn {
name: "from_int".to_string(),
field_map: None,
module: "".to_string(),
arity: 2,
location: Span::empty(),
builtin: None,
},
),
);
// do_from_int
prelude.values.insert(
"do_from_int".to_string(),
ValueConstructor::public(
function(vec![int(), byte_array()], byte_array()),
ValueConstructorVariant::ModuleFn {
name: "do_from_int".to_string(),
field_map: None,
module: "".to_string(),
arity: 2,
location: Span::empty(),
builtin: None,
},
),
);
// diagnostic
prelude.values.insert(
"diagnostic".to_string(),
ValueConstructor::public(
function(vec![data(), byte_array()], byte_array()),
ValueConstructorVariant::ModuleFn {
name: "diagnostic".to_string(),
field_map: None,
module: "".to_string(),
arity: 2,
location: Span::empty(),
builtin: None,
},
),
);
// always // always
let always_a_var = generic_var(id_gen.next()); let always_a_var = generic_var(id_gen.next());
let always_b_var = generic_var(id_gen.next()); let always_b_var = generic_var(id_gen.next());
@ -919,7 +1016,10 @@ pub fn from_default_function(builtin: DefaultFunction, id_gen: &IdGenerator) ->
) )
} }
pub fn prelude_functions(id_gen: &IdGenerator) -> IndexMap<FunctionAccessKey, TypedFunction> { pub fn prelude_functions(
id_gen: &IdGenerator,
module_types: &HashMap<String, TypeInfo>,
) -> IndexMap<FunctionAccessKey, TypedFunction> {
let mut functions = IndexMap::new(); let mut functions = IndexMap::new();
// /// Negate the argument. Useful for map/fold and pipelines. // /// Negate the argument. Useful for map/fold and pipelines.
@ -1239,6 +1339,230 @@ pub fn prelude_functions(id_gen: &IdGenerator) -> IndexMap<FunctionAccessKey, Ty
}, },
); );
functions.insert(
FunctionAccessKey {
module_name: "".to_string(),
function_name: "enumerate".to_string(),
},
aiken_fn!(
&module_types,
&id_gen,
r#"
fn enumerate(
self: List<a>,
zero: b,
with: fn(a, b) -> b,
last: fn(a, b) -> b,
) -> b {
when self is {
[] -> zero
[x] -> last(x, zero)
[x, ..xs] -> with(x, enumerate(xs, zero, with, last))
}
}
"#
),
);
functions.insert(
FunctionAccessKey {
module_name: "".to_string(),
function_name: "encode_base16".to_string(),
},
aiken_fn!(
&module_types,
&id_gen,
r#"
use aiken/builtin
fn encode_base16(bytes: ByteArray, ix: Int, builder: ByteArray) -> ByteArray {
if ix < 0 {
builder
} else {
let byte = builtin.index_bytearray(bytes, ix)
let msb = byte / 16
let lsb = byte % 16
let builder =
builtin.cons_bytearray(
msb + if msb < 10 {
48
} else {
55
},
builtin.cons_bytearray(
lsb + if lsb < 10 {
48
} else {
55
},
builder,
),
)
encode_base16(bytes, ix - 1, builder)
}
}
"#
),
);
functions.insert(
FunctionAccessKey {
module_name: "".to_string(),
function_name: "do_from_int".to_string(),
},
aiken_fn!(
&module_types,
&id_gen,
r#"
use aiken/builtin
fn do_from_int(i: Int, digits: ByteArray) -> ByteArray {
if i <= 0 {
digits
} else {
do_from_int(
builtin.quotient_integer(i, 10),
builtin.cons_bytearray(builtin.remainder_integer(i, 10) + 48, digits),
)
}
}
"#
),
);
functions.insert(
FunctionAccessKey {
module_name: "".to_string(),
function_name: "from_int".to_string(),
},
aiken_fn!(
&module_types,
&id_gen,
r#"
use aiken/builtin
/// Encode an integer into UTF-8.
fn from_int(i: Int, digits: ByteArray) -> ByteArray {
if i == 0 {
builtin.append_bytearray(#"30", digits)
} else if i < 0 {
builtin.append_bytearray(#"2d", from_int(-i, digits))
} else {
do_from_int(
builtin.quotient_integer(i, 10),
builtin.cons_bytearray(builtin.remainder_integer(i, 10) + 48, digits),
)
}
}
"#
),
);
functions.insert(
FunctionAccessKey {
module_name: "".to_string(),
function_name: "diagnostic".to_string(),
},
aiken_fn!(
&module_types,
&id_gen,
r#"
use aiken/builtin
fn diagnostic(self: Data, builder: ByteArray) -> ByteArray {
builtin.choose_data(
self,
{
let Pair(constr, fields) = builtin.un_constr_data(self)
let builder =
when fields is {
[] -> builtin.append_bytearray(#"5b5d29", builder)
_ -> {
let bytes =
enumerate(
fields,
builtin.append_bytearray(#"5d29", builder),
fn(e: Data, st: ByteArray) {
diagnostic(e, builtin.append_bytearray(#"2c20", st))
},
fn(e: Data, st: ByteArray) { diagnostic(e, st) },
)
builtin.append_bytearray(#"5b5f20", bytes)
}
}
let constr_tag =
if constr < 7 {
121 + constr
} else if constr < 128 {
1280 + constr - 7
} else {
fail @"What are you doing? No I mean, seriously."
}
builder
|> builtin.append_bytearray(#"28", _)
|> from_int(constr_tag, _)
},
{
let elems = builtin.un_map_data(self)
when elems is {
[] -> builtin.append_bytearray(#"7b7d", builder)
_ -> {
let bytes =
enumerate(
elems,
builtin.append_bytearray(#"207d", builder),
fn(e: Pair<Data, Data>, st: ByteArray) {
let value = diagnostic(e.2nd, builtin.append_bytearray(#"2c20", st))
diagnostic(e.1st, builtin.append_bytearray(#"3a20", value))
},
fn(e: Pair<Data, Data>, st: ByteArray) {
let value = diagnostic(e.2nd, st)
diagnostic(e.1st, builtin.append_bytearray(#"3a20", value))
},
)
builtin.append_bytearray(#"7b5f20", bytes)
}
}
},
{
let elems = builtin.un_list_data(self)
when elems is {
[] -> builtin.append_bytearray(#"5b5d", builder)
_ -> {
let bytes =
enumerate(
elems,
builtin.append_bytearray(#"5d", builder),
fn(e: Data, st: ByteArray) {
diagnostic(e, builtin.append_bytearray(#"2c20", st))
},
fn(e: Data, st: ByteArray) { diagnostic(e, st) },
)
builtin.append_bytearray(#"5b5f20", bytes)
}
}
},
self
|> builtin.un_i_data
|> from_int(builder),
{
let bytes = builtin.un_b_data(self)
bytes
|> encode_base16(
builtin.length_of_bytearray(bytes) - 1,
builtin.append_bytearray(#"27", builder),
)
|> builtin.append_bytearray(#"6827", _)
},
)
}
"#
),
);
functions functions
} }

View File

@ -31,5 +31,36 @@ impl IdGenerator {
} }
} }
#[macro_export]
macro_rules! aiken_fn {
($module_types:expr, $id_gen:expr, $src:expr) => {{
let (untyped_module, _) = $crate::parser::module($src, $crate::ast::ModuleKind::Lib)
.expect("failed to parse module.");
let module_name = "";
let mut warnings = vec![];
let typed_module = untyped_module
.infer(
$id_gen,
$crate::ast::ModuleKind::Lib,
module_name,
$module_types,
$crate::ast::Tracing::silent(),
&mut warnings,
)
.unwrap();
if let Some($crate::ast::Definition::Fn(typed_fn)) =
typed_module.definitions.into_iter().last()
{
typed_fn
} else {
unreachable!()
}
}};
}
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;

View File

@ -12,10 +12,10 @@ use itertools::Itertools;
use std::{cell::RefCell, collections::HashMap, ops::Deref, rc::Rc}; use std::{cell::RefCell, collections::HashMap, ops::Deref, rc::Rc};
use uplc::{ast::Type as UplcType, builtins::DefaultFunction}; use uplc::{ast::Type as UplcType, builtins::DefaultFunction};
mod environment; pub(crate) mod environment;
pub mod error; pub mod error;
mod exhaustive; mod exhaustive;
mod expr; pub(crate) mod expr;
pub mod fields; pub mod fields;
mod hydrator; mod hydrator;
mod infer; mod infer;

View File

@ -96,7 +96,7 @@ pub(crate) fn infer_function(
let preregistered_fn = environment let preregistered_fn = environment
.get_variable(name) .get_variable(name)
.expect("Could not find preregistered type for function"); .unwrap_or_else(|| panic!("Could not find preregistered type for function: {name}"));
let field_map = preregistered_fn.field_map().cloned(); let field_map = preregistered_fn.field_map().cloned();
@ -1797,7 +1797,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
}) })
} }
fn infer_fn( pub fn infer_fn(
&mut self, &mut self,
args: Vec<UntypedArg>, args: Vec<UntypedArg>,
expected_args: &[Rc<Type>], expected_args: &[Rc<Type>],
@ -2435,9 +2435,12 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
tipo: string(), tipo: string(),
value: if ix == 0 { ": " } else { ", " }.to_string(), value: if ix == 0 { ": " } else { ", " }.to_string(),
}; };
typed_arguments.into_iter().enumerate().fold(label, |text, (ix, arg)| { typed_arguments
append_string_expr(append_string_expr(text, delimiter(ix)), arg) .into_iter()
}) .enumerate()
.fold(label, |text, (ix, arg)| {
append_string_expr(append_string_expr(text, delimiter(ix)), arg)
})
}; };
let then = self.infer(then)?; let then = self.infer(then)?;
@ -2813,7 +2816,8 @@ pub fn ensure_serialisable(is_top_level: bool, t: Rc<Type>, location: Span) -> R
} }
pub fn append_string_expr(left: TypedExpr, right: TypedExpr) -> TypedExpr { pub fn append_string_expr(left: TypedExpr, right: TypedExpr) -> TypedExpr {
let value_constructor = from_default_function(DefaultFunction::AppendString, &IdGenerator::new()); let value_constructor =
from_default_function(DefaultFunction::AppendString, &IdGenerator::new());
let append_string = TypedExpr::ModuleSelect { let append_string = TypedExpr::ModuleSelect {
location: Span::empty(), location: Span::empty(),
tipo: value_constructor.tipo, tipo: value_constructor.tipo,
@ -2827,13 +2831,11 @@ pub fn append_string_expr(left: TypedExpr, right: TypedExpr) -> TypedExpr {
// So this is merely a small work-around for convenience. The proper way here would be to // So this is merely a small work-around for convenience. The proper way here would be to
// pull the function definition for append_string from the pre-registered builtins // pull the function definition for append_string from the pre-registered builtins
// functions somewhere in the environment. // functions somewhere in the environment.
constructor: value_constructor constructor: value_constructor.variant.to_module_value_constructor(
.variant string(),
.to_module_value_constructor( BUILTIN,
string(), &DefaultFunction::AppendString.aiken_name(),
BUILTIN, ),
&DefaultFunction::AppendString.aiken_name(),
),
}; };
TypedExpr::Call { TypedExpr::Call {

View File

@ -9,7 +9,7 @@ Schema {
Var { Var {
tipo: RefCell { tipo: RefCell {
value: Generic { value: Generic {
id: 35, id: 64,
}, },
}, },
alias: None, alias: None,

View File

@ -130,7 +130,7 @@ where
module_types.insert("aiken".to_string(), builtins::prelude(&id_gen)); module_types.insert("aiken".to_string(), builtins::prelude(&id_gen));
module_types.insert("aiken/builtin".to_string(), builtins::plutus(&id_gen)); module_types.insert("aiken/builtin".to_string(), builtins::plutus(&id_gen));
let functions = builtins::prelude_functions(&id_gen); let functions = builtins::prelude_functions(&id_gen, &module_types);
let data_types = builtins::prelude_data_types(&id_gen); let data_types = builtins::prelude_data_types(&id_gen);

View File

@ -9,7 +9,7 @@ Schema {
Var { Var {
tipo: RefCell { tipo: RefCell {
value: Generic { value: Generic {
id: 35, id: 64,
}, },
}, },
alias: None, alias: None,

View File

@ -1328,7 +1328,7 @@ mod test {
.last() .last()
.expect("No test found in declared src?"); .expect("No test found in declared src?");
let mut functions = builtins::prelude_functions(&id_gen); let mut functions = builtins::prelude_functions(&id_gen, &module_types);
let mut data_types = builtins::prelude_data_types(&id_gen); let mut data_types = builtins::prelude_data_types(&id_gen);
ast.register_definitions(&mut functions, &mut data_types); ast.register_definitions(&mut functions, &mut data_types);

View File

@ -46,7 +46,7 @@ impl TestProject {
module_types.insert("aiken".to_string(), builtins::prelude(&id_gen)); module_types.insert("aiken".to_string(), builtins::prelude(&id_gen));
module_types.insert("aiken/builtin".to_string(), builtins::plutus(&id_gen)); module_types.insert("aiken/builtin".to_string(), builtins::plutus(&id_gen));
let functions = builtins::prelude_functions(&id_gen); let functions = builtins::prelude_functions(&id_gen, &module_types);
let data_types = builtins::prelude_data_types(&id_gen); let data_types = builtins::prelude_data_types(&id_gen);
TestProject { TestProject {