diff --git a/crates/aiken-lang/src/builtins.rs b/crates/aiken-lang/src/builtins.rs index 0975965c..3d9858cf 100644 --- a/crates/aiken-lang/src/builtins.rs +++ b/crates/aiken-lang/src/builtins.rs @@ -1,4 +1,5 @@ use crate::{ + aiken_fn, ast::{ Annotation, ArgName, CallArg, DataTypeKey, Function, FunctionAccessKey, ModuleKind, 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 let always_a_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 { +pub fn prelude_functions( + id_gen: &IdGenerator, + module_types: &HashMap, +) -> IndexMap { let mut functions = IndexMap::new(); // /// Negate the argument. Useful for map/fold and pipelines. @@ -1239,6 +1339,230 @@ pub fn prelude_functions(id_gen: &IdGenerator) -> IndexMap, + 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, st: ByteArray) { + let value = diagnostic(e.2nd, builtin.append_bytearray(#"2c20", st)) + diagnostic(e.1st, builtin.append_bytearray(#"3a20", value)) + }, + fn(e: Pair, 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 } diff --git a/crates/aiken-lang/src/lib.rs b/crates/aiken-lang/src/lib.rs index 5f52ceb7..2910c3f3 100644 --- a/crates/aiken-lang/src/lib.rs +++ b/crates/aiken-lang/src/lib.rs @@ -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)] mod tests; diff --git a/crates/aiken-lang/src/tipo.rs b/crates/aiken-lang/src/tipo.rs index f29257c3..7b8274cf 100644 --- a/crates/aiken-lang/src/tipo.rs +++ b/crates/aiken-lang/src/tipo.rs @@ -12,10 +12,10 @@ use itertools::Itertools; use std::{cell::RefCell, collections::HashMap, ops::Deref, rc::Rc}; use uplc::{ast::Type as UplcType, builtins::DefaultFunction}; -mod environment; +pub(crate) mod environment; pub mod error; mod exhaustive; -mod expr; +pub(crate) mod expr; pub mod fields; mod hydrator; mod infer; diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index cd6cc184..697d676b 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -96,7 +96,7 @@ pub(crate) fn infer_function( let preregistered_fn = environment .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(); @@ -1797,7 +1797,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { }) } - fn infer_fn( + pub fn infer_fn( &mut self, args: Vec, expected_args: &[Rc], @@ -2435,9 +2435,12 @@ impl<'a, 'b> ExprTyper<'a, 'b> { tipo: string(), value: if ix == 0 { ": " } else { ", " }.to_string(), }; - typed_arguments.into_iter().enumerate().fold(label, |text, (ix, arg)| { - append_string_expr(append_string_expr(text, delimiter(ix)), arg) - }) + typed_arguments + .into_iter() + .enumerate() + .fold(label, |text, (ix, arg)| { + append_string_expr(append_string_expr(text, delimiter(ix)), arg) + }) }; let then = self.infer(then)?; @@ -2813,7 +2816,8 @@ pub fn ensure_serialisable(is_top_level: bool, t: Rc, location: Span) -> R } 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 { location: Span::empty(), 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 // pull the function definition for append_string from the pre-registered builtins // functions somewhere in the environment. - constructor: value_constructor - .variant - .to_module_value_constructor( - string(), - BUILTIN, - &DefaultFunction::AppendString.aiken_name(), - ), + constructor: value_constructor.variant.to_module_value_constructor( + string(), + BUILTIN, + &DefaultFunction::AppendString.aiken_name(), + ), }; TypedExpr::Call { diff --git a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__free_vars.snap b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__free_vars.snap index af354a21..8f64d017 100644 --- a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__free_vars.snap +++ b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__free_vars.snap @@ -9,7 +9,7 @@ Schema { Var { tipo: RefCell { value: Generic { - id: 35, + id: 64, }, }, alias: None, diff --git a/crates/aiken-project/src/lib.rs b/crates/aiken-project/src/lib.rs index 93196f5f..6efab615 100644 --- a/crates/aiken-project/src/lib.rs +++ b/crates/aiken-project/src/lib.rs @@ -130,7 +130,7 @@ where module_types.insert("aiken".to_string(), builtins::prelude(&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); diff --git a/crates/aiken-project/src/snapshots/aiken_project__export__tests__cannot_export_generics.snap b/crates/aiken-project/src/snapshots/aiken_project__export__tests__cannot_export_generics.snap index 69be98f0..32ab8881 100644 --- a/crates/aiken-project/src/snapshots/aiken_project__export__tests__cannot_export_generics.snap +++ b/crates/aiken-project/src/snapshots/aiken_project__export__tests__cannot_export_generics.snap @@ -9,7 +9,7 @@ Schema { Var { tipo: RefCell { value: Generic { - id: 35, + id: 64, }, }, alias: None, diff --git a/crates/aiken-project/src/test_framework.rs b/crates/aiken-project/src/test_framework.rs index a9ccebb9..15d1f57d 100644 --- a/crates/aiken-project/src/test_framework.rs +++ b/crates/aiken-project/src/test_framework.rs @@ -1328,7 +1328,7 @@ mod test { .last() .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); ast.register_definitions(&mut functions, &mut data_types); diff --git a/crates/aiken-project/src/tests/mod.rs b/crates/aiken-project/src/tests/mod.rs index 94ddf664..d0967542 100644 --- a/crates/aiken-project/src/tests/mod.rs +++ b/crates/aiken-project/src/tests/mod.rs @@ -46,7 +46,7 @@ impl TestProject { module_types.insert("aiken".to_string(), builtins::prelude(&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); TestProject {