2472 lines
74 KiB
Rust
2472 lines
74 KiB
Rust
pub mod well_known;
|
|
|
|
use crate::{
|
|
ast::well_known::VALIDATOR_ELSE,
|
|
expr::{TypedExpr, UntypedExpr},
|
|
line_numbers::LineNumbers,
|
|
parser::token::{Base, Token},
|
|
tipo::{PatternConstructor, Type, TypeInfo},
|
|
};
|
|
use indexmap::IndexMap;
|
|
use miette::Diagnostic;
|
|
use ordinal::Ordinal;
|
|
use owo_colors::{OwoColorize, Stream::Stdout};
|
|
use std::{
|
|
fmt::{self, Display},
|
|
ops::Range,
|
|
rc::Rc,
|
|
};
|
|
use uplc::machine::runtime::Compressable;
|
|
use vec1::{vec1, Vec1};
|
|
|
|
pub const BACKPASS_VARIABLE: &str = "_backpass";
|
|
pub const CAPTURE_VARIABLE: &str = "_capture";
|
|
pub const PIPE_VARIABLE: &str = "_pipe";
|
|
|
|
pub const ENV_MODULE: &str = "env";
|
|
pub const CONFIG_MODULE: &str = "config";
|
|
pub const DEFAULT_ENV_MODULE: &str = "default";
|
|
|
|
pub const HANDLER_SPEND: &str = "spend";
|
|
pub const HANDLER_MINT: &str = "mint";
|
|
pub const HANDLER_WITHDRAW: &str = "withdraw";
|
|
pub const HANDLER_PUBLISH: &str = "publish";
|
|
pub const HANDLER_VOTE: &str = "vote";
|
|
pub const HANDLER_PROPOSE: &str = "propose";
|
|
|
|
pub type TypedModule = Module<TypeInfo, TypedDefinition>;
|
|
pub type UntypedModule = Module<(), UntypedDefinition>;
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
|
pub enum ModuleKind {
|
|
Lib,
|
|
Validator,
|
|
Env,
|
|
Config,
|
|
}
|
|
|
|
impl ModuleKind {
|
|
pub fn is_validator(&self) -> bool {
|
|
matches!(self, ModuleKind::Validator)
|
|
}
|
|
|
|
pub fn is_lib(&self) -> bool {
|
|
matches!(self, ModuleKind::Lib)
|
|
}
|
|
|
|
pub fn is_env(&self) -> bool {
|
|
matches!(self, ModuleKind::Env)
|
|
}
|
|
|
|
pub fn is_config(&self) -> bool {
|
|
matches!(self, ModuleKind::Config)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
|
pub struct Module<Info, Definitions> {
|
|
pub name: String,
|
|
pub docs: Vec<String>,
|
|
pub type_info: Info,
|
|
pub definitions: Vec<Definitions>,
|
|
pub lines: LineNumbers,
|
|
pub kind: ModuleKind,
|
|
}
|
|
|
|
impl<Info, Definitions> Module<Info, Definitions> {
|
|
pub fn definitions(&self) -> impl Iterator<Item = &Definitions> {
|
|
self.definitions.iter()
|
|
}
|
|
|
|
pub fn into_definitions(self) -> impl Iterator<Item = Definitions> {
|
|
self.definitions.into_iter()
|
|
}
|
|
}
|
|
|
|
impl UntypedModule {
|
|
pub fn dependencies(&self, env_modules: &[String]) -> Vec<String> {
|
|
self.definitions()
|
|
.flat_map(|def| {
|
|
if let Definition::Use(Use { module, .. }) = def {
|
|
let name = module.join("/");
|
|
if name == ENV_MODULE {
|
|
env_modules.to_vec()
|
|
} else {
|
|
vec![name]
|
|
}
|
|
} else {
|
|
Vec::new()
|
|
}
|
|
})
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
impl TypedModule {
|
|
pub fn find_node(&self, byte_index: usize) -> Option<Located<'_>> {
|
|
self.definitions
|
|
.iter()
|
|
.find_map(|definition| definition.find_node(byte_index))
|
|
}
|
|
|
|
pub fn has_definition(&self, name: &str) -> bool {
|
|
self.definitions.iter().any(|def| match def {
|
|
Definition::Fn(f) => f.public && f.name == name,
|
|
Definition::TypeAlias(alias) => alias.public && alias.alias == name,
|
|
Definition::ModuleConstant(cst) => cst.public && cst.name == name,
|
|
Definition::DataType(t) => t.public && t.name == name,
|
|
Definition::Use(_) => false,
|
|
Definition::Test(_) => false,
|
|
Definition::Validator(_) => false,
|
|
})
|
|
}
|
|
|
|
pub fn has_constructor(&self, name: &str) -> bool {
|
|
self.definitions.iter().any(|def| match def {
|
|
Definition::DataType(t) if t.public && !t.opaque => t
|
|
.constructors
|
|
.iter()
|
|
.any(|constructor| constructor.name == name),
|
|
Definition::DataType(_) => false,
|
|
Definition::Fn(_) => false,
|
|
Definition::TypeAlias(_) => false,
|
|
Definition::ModuleConstant(_) => false,
|
|
Definition::Use(_) => false,
|
|
Definition::Test(_) => false,
|
|
Definition::Validator(_) => false,
|
|
})
|
|
}
|
|
|
|
pub fn validate_module_name(&self) -> Result<(), Error> {
|
|
if self.name == "aiken" || self.name == "aiken/builtin" {
|
|
return Err(Error::ReservedModuleName {
|
|
name: self.name.to_string(),
|
|
});
|
|
};
|
|
|
|
for segment in self.name.split('/') {
|
|
if str_to_keyword(segment).is_some() {
|
|
return Err(Error::KeywordInModuleName {
|
|
name: self.name.to_string(),
|
|
keyword: segment.to_string(),
|
|
});
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// TODO: Avoid cloning definitions here. This would likely require having a lifetime on
|
|
// 'Project', so that we can enforce that those references live from the ast to here.
|
|
pub fn register_definitions(
|
|
&self,
|
|
functions: &mut IndexMap<FunctionAccessKey, TypedFunction>,
|
|
constants: &mut IndexMap<FunctionAccessKey, TypedExpr>,
|
|
data_types: &mut IndexMap<DataTypeKey, TypedDataType>,
|
|
) {
|
|
for def in self.definitions() {
|
|
match def {
|
|
Definition::Fn(func) => {
|
|
functions.insert(
|
|
FunctionAccessKey {
|
|
module_name: self.name.clone(),
|
|
function_name: func.name.clone(),
|
|
},
|
|
func.clone(),
|
|
);
|
|
}
|
|
|
|
Definition::Test(test) => {
|
|
functions.insert(
|
|
FunctionAccessKey {
|
|
module_name: self.name.clone(),
|
|
function_name: test.name.clone(),
|
|
},
|
|
test.clone().into(),
|
|
);
|
|
}
|
|
|
|
Definition::DataType(dt) => {
|
|
data_types.insert(
|
|
DataTypeKey {
|
|
module_name: self.name.clone(),
|
|
defined_type: dt.name.clone(),
|
|
},
|
|
dt.clone(),
|
|
);
|
|
}
|
|
|
|
Definition::Validator(v) => {
|
|
let module_name = self.name.as_str();
|
|
|
|
for (k, v) in v.into_function_definitions(module_name) {
|
|
functions.insert(k, v);
|
|
}
|
|
}
|
|
|
|
Definition::ModuleConstant(ModuleConstant { name, value, .. }) => {
|
|
constants.insert(
|
|
FunctionAccessKey {
|
|
module_name: self.name.clone(),
|
|
function_name: name.clone(),
|
|
},
|
|
value.clone(),
|
|
);
|
|
}
|
|
|
|
Definition::TypeAlias(_) | Definition::Use(_) => {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn str_to_keyword(word: &str) -> Option<Token> {
|
|
// Alphabetical keywords:
|
|
match word {
|
|
"expect" => Some(Token::Expect),
|
|
"else" => Some(Token::Else),
|
|
"is" => Some(Token::Is),
|
|
"as" => Some(Token::As),
|
|
"when" => Some(Token::When),
|
|
"const" => Some(Token::Const),
|
|
"fn" => Some(Token::Fn),
|
|
"if" => Some(Token::If),
|
|
"use" => Some(Token::Use),
|
|
"let" => Some(Token::Let),
|
|
"opaque" => Some(Token::Opaque),
|
|
"pub" => Some(Token::Pub),
|
|
"todo" => Some(Token::Todo),
|
|
"type" => Some(Token::Type),
|
|
"trace" => Some(Token::Trace),
|
|
"test" => Some(Token::Test),
|
|
// TODO: remove this in a future release
|
|
"error" => Some(Token::Fail),
|
|
"fail" => Some(Token::Fail),
|
|
"and" => Some(Token::And),
|
|
"or" => Some(Token::Or),
|
|
"validator" => Some(Token::Validator),
|
|
"via" => Some(Token::Via),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub type TypedFunction = Function<Rc<Type>, TypedExpr, TypedArg>;
|
|
pub type UntypedFunction = Function<(), UntypedExpr, UntypedArg>;
|
|
|
|
impl UntypedFunction {
|
|
pub fn is_default_fallback(&self) -> bool {
|
|
matches!(
|
|
&self.arguments[..],
|
|
[UntypedArg {
|
|
by: ArgBy::ByName(ArgName::Discarded { .. }),
|
|
..
|
|
}]
|
|
) && matches!(&self.body, UntypedExpr::ErrorTerm { .. })
|
|
&& self.name.as_str() == well_known::VALIDATOR_ELSE
|
|
}
|
|
}
|
|
|
|
pub type TypedTest = Function<Rc<Type>, TypedExpr, TypedArgVia>;
|
|
pub type UntypedTest = Function<(), UntypedExpr, UntypedArgVia>;
|
|
|
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
pub enum OnTestFailure {
|
|
FailImmediately,
|
|
SucceedImmediately,
|
|
SucceedEventually,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
pub struct Function<T, Expr, Arg> {
|
|
pub arguments: Vec<Arg>,
|
|
pub body: Expr,
|
|
pub doc: Option<String>,
|
|
pub location: Span,
|
|
pub name: String,
|
|
pub public: bool,
|
|
pub return_annotation: Option<Annotation>,
|
|
pub return_type: T,
|
|
pub end_position: usize,
|
|
pub on_test_failure: OnTestFailure,
|
|
}
|
|
|
|
impl<T, Expr, Arg> Function<T, Expr, Arg> {
|
|
pub fn is_spend(&self) -> bool {
|
|
self.name == HANDLER_SPEND
|
|
}
|
|
|
|
pub fn is_mint(&self) -> bool {
|
|
self.name == HANDLER_MINT
|
|
}
|
|
}
|
|
|
|
impl TypedFunction {
|
|
pub fn find_node(&self, byte_index: usize) -> Option<Located<'_>> {
|
|
self.arguments
|
|
.iter()
|
|
.find_map(|arg| arg.find_node(byte_index))
|
|
.or_else(|| self.body.find_node(byte_index))
|
|
.or_else(|| {
|
|
self.return_annotation
|
|
.as_ref()
|
|
.and_then(|a| a.find_node(byte_index))
|
|
})
|
|
}
|
|
|
|
pub fn has_valid_purpose_name(&self) -> bool {
|
|
self.name == HANDLER_SPEND
|
|
|| self.name == HANDLER_PUBLISH
|
|
|| self.name == HANDLER_PROPOSE
|
|
|| self.name == HANDLER_MINT
|
|
|| self.name == HANDLER_WITHDRAW
|
|
|| self.name == HANDLER_VOTE
|
|
}
|
|
|
|
pub fn validator_arity(&self) -> usize {
|
|
if self.name == HANDLER_SPEND {
|
|
4
|
|
} else if self.name == HANDLER_MINT
|
|
|| self.name == HANDLER_WITHDRAW
|
|
|| self.name == HANDLER_VOTE
|
|
|| self.name == HANDLER_PUBLISH
|
|
|| self.name == HANDLER_PROPOSE
|
|
{
|
|
3
|
|
} else {
|
|
panic!(
|
|
"tried to get validator arity of a non-validator function {}",
|
|
&self.name
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TypedTest {
|
|
pub fn find_node(&self, byte_index: usize) -> Option<Located<'_>> {
|
|
self.arguments
|
|
.iter()
|
|
.find_map(|arg| arg.find_node(byte_index))
|
|
.or_else(|| self.body.find_node(byte_index))
|
|
.or_else(|| {
|
|
self.return_annotation
|
|
.as_ref()
|
|
.and_then(|a| a.find_node(byte_index))
|
|
})
|
|
}
|
|
}
|
|
|
|
pub type TypedTypeAlias = TypeAlias<Rc<Type>>;
|
|
pub type UntypedTypeAlias = TypeAlias<()>;
|
|
|
|
impl From<UntypedTest> for UntypedFunction {
|
|
fn from(f: UntypedTest) -> Self {
|
|
Function {
|
|
doc: f.doc,
|
|
location: f.location,
|
|
name: f.name,
|
|
public: f.public,
|
|
arguments: f.arguments.into_iter().map(|arg| arg.into()).collect(),
|
|
return_annotation: f.return_annotation,
|
|
return_type: f.return_type,
|
|
body: f.body,
|
|
on_test_failure: f.on_test_failure,
|
|
end_position: f.end_position,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<TypedTest> for TypedFunction {
|
|
fn from(f: TypedTest) -> Self {
|
|
Function {
|
|
doc: f.doc,
|
|
location: f.location,
|
|
name: f.name,
|
|
public: f.public,
|
|
arguments: f.arguments.into_iter().map(|arg| arg.into()).collect(),
|
|
return_annotation: f.return_annotation,
|
|
return_type: f.return_type,
|
|
body: f.body,
|
|
on_test_failure: f.on_test_failure,
|
|
end_position: f.end_position,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
pub struct TypeAlias<T> {
|
|
pub alias: String,
|
|
pub annotation: Annotation,
|
|
pub doc: Option<String>,
|
|
pub location: Span,
|
|
pub parameters: Vec<String>,
|
|
pub public: bool,
|
|
pub tipo: T,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
|
pub struct DataTypeKey {
|
|
pub module_name: String,
|
|
pub defined_type: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
|
pub struct FunctionAccessKey {
|
|
pub module_name: String,
|
|
pub function_name: String,
|
|
}
|
|
|
|
pub type UntypedDataType = DataType<()>;
|
|
pub type TypedDataType = DataType<Rc<Type>>;
|
|
|
|
impl TypedDataType {
|
|
pub fn known_enum(name: &str, constructors: &[&str]) -> Self {
|
|
Self::known_data_type(name, &RecordConstructor::known_enum(constructors))
|
|
}
|
|
|
|
pub fn known_data_type(name: &str, constructors: &[RecordConstructor<Rc<Type>>]) -> Self {
|
|
Self {
|
|
name: name.to_string(),
|
|
constructors: constructors.to_vec(),
|
|
location: Span::empty(),
|
|
opaque: false,
|
|
public: true,
|
|
parameters: vec![],
|
|
typed_parameters: vec![],
|
|
doc: None,
|
|
}
|
|
}
|
|
|
|
pub fn is_never(&self) -> bool {
|
|
self.name == well_known::NEVER
|
|
&& self.constructors.len() == well_known::NEVER_CONSTRUCTORS.len()
|
|
&& self.location == Span::empty()
|
|
&& self
|
|
.constructors
|
|
.iter()
|
|
.zip(well_known::NEVER_CONSTRUCTORS)
|
|
.all(|(constructor, name)| {
|
|
name == &constructor.name && constructor.arguments.is_empty()
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
pub struct DataType<T> {
|
|
pub constructors: Vec<RecordConstructor<T>>,
|
|
pub doc: Option<String>,
|
|
pub location: Span,
|
|
pub name: String,
|
|
pub opaque: bool,
|
|
pub parameters: Vec<String>,
|
|
pub public: bool,
|
|
pub typed_parameters: Vec<T>,
|
|
}
|
|
|
|
pub type TypedUse = Use<String>;
|
|
pub type UntypedUse = Use<()>;
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
|
pub struct Use<PackageName> {
|
|
pub as_name: Option<String>,
|
|
pub location: Span,
|
|
pub module: Vec<String>,
|
|
pub package: PackageName,
|
|
pub unqualified: Vec<UnqualifiedImport>,
|
|
}
|
|
|
|
pub type TypedModuleConstant = ModuleConstant<TypedExpr>;
|
|
pub type UntypedModuleConstant = ModuleConstant<UntypedExpr>;
|
|
|
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
pub struct ModuleConstant<Expr> {
|
|
pub doc: Option<String>,
|
|
pub location: Span,
|
|
pub public: bool,
|
|
pub name: String,
|
|
pub annotation: Option<Annotation>,
|
|
pub value: Expr,
|
|
}
|
|
|
|
pub type TypedValidator = Validator<Rc<Type>, TypedArg, TypedExpr>;
|
|
pub type UntypedValidator = Validator<(), UntypedArg, UntypedExpr>;
|
|
|
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
pub enum Purpose {
|
|
Spend,
|
|
Mint,
|
|
Withdraw,
|
|
Publish,
|
|
Propose,
|
|
Vote,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
pub struct Validator<T, Arg, Expr> {
|
|
pub doc: Option<String>,
|
|
pub end_position: usize,
|
|
pub handlers: Vec<Function<T, Expr, Arg>>,
|
|
pub location: Span,
|
|
pub name: String,
|
|
pub params: Vec<Arg>,
|
|
pub fallback: Function<T, Expr, Arg>,
|
|
}
|
|
|
|
impl<T, Arg, Expr> Validator<T, Arg, Expr> {
|
|
pub fn handler_name(validator: &str, handler: &str) -> String {
|
|
format!("{}.{}", validator, handler)
|
|
}
|
|
}
|
|
|
|
impl UntypedValidator {
|
|
pub fn default_fallback(location: Span) -> UntypedFunction {
|
|
Function {
|
|
arguments: vec![UntypedArg {
|
|
by: ArgBy::ByName(ArgName::Discarded {
|
|
name: "_".to_string(),
|
|
label: "_".to_string(),
|
|
location,
|
|
}),
|
|
location,
|
|
annotation: None,
|
|
doc: None,
|
|
is_validator_param: false,
|
|
}],
|
|
body: UntypedExpr::fail(None, location),
|
|
doc: None,
|
|
location,
|
|
end_position: location.end - 1,
|
|
name: well_known::VALIDATOR_ELSE.to_string(),
|
|
public: true,
|
|
return_annotation: Some(Annotation::boolean(location)),
|
|
return_type: (),
|
|
on_test_failure: OnTestFailure::FailImmediately,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TypedValidator {
|
|
pub fn available_handler_names() -> Vec<String> {
|
|
vec![
|
|
HANDLER_SPEND.to_string(),
|
|
HANDLER_MINT.to_string(),
|
|
HANDLER_WITHDRAW.to_string(),
|
|
HANDLER_PUBLISH.to_string(),
|
|
HANDLER_VOTE.to_string(),
|
|
HANDLER_PROPOSE.to_string(),
|
|
VALIDATOR_ELSE.to_string(),
|
|
]
|
|
}
|
|
|
|
// Define a validator wrapper extracting and matching on script purpose for
|
|
// users.
|
|
pub fn into_script_context_handler(&self) -> TypedExpr {
|
|
let var_context = "__context__";
|
|
let var_transaction = "__transaction__";
|
|
let var_redeemer = "__redeemer__";
|
|
let var_purpose = "__purpose__";
|
|
let var_purpose_arg = "__purpose_arg__";
|
|
let var_datum = "__datum__";
|
|
|
|
TypedExpr::sequence(&[
|
|
TypedExpr::let_(
|
|
TypedExpr::local_var(var_context, Type::script_context(), self.location),
|
|
TypedPattern::Constructor {
|
|
is_record: false,
|
|
location: Span::empty(),
|
|
name: well_known::SCRIPT_CONTEXT_CONSTRUCTORS[0].to_string(),
|
|
arguments: vec![
|
|
CallArg::var(var_transaction, Span::empty()),
|
|
CallArg::var(var_redeemer, Span::empty()),
|
|
CallArg::var(var_purpose, Span::empty()),
|
|
],
|
|
module: None,
|
|
constructor: PatternConstructor::Record {
|
|
name: well_known::SCRIPT_CONTEXT_CONSTRUCTORS[0].to_string(),
|
|
field_map: None,
|
|
},
|
|
spread_location: None,
|
|
tipo: Type::function(
|
|
vec![Type::data(), Type::data(), Type::script_purpose()],
|
|
Type::data(),
|
|
),
|
|
},
|
|
Type::script_context(),
|
|
Span::empty(),
|
|
),
|
|
TypedExpr::When {
|
|
location: Span::empty(),
|
|
tipo: Type::bool(),
|
|
subject: TypedExpr::local_var(var_purpose, Type::script_purpose(), Span::empty())
|
|
.into(),
|
|
clauses: self
|
|
.handlers
|
|
.iter()
|
|
.map(|handler| {
|
|
let datum = if handler.name.as_str() == "spend" {
|
|
handler.arguments.first()
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let redeemer = handler
|
|
.arguments
|
|
.get(if datum.is_some() { 1 } else { 0 })
|
|
.unwrap();
|
|
|
|
let purpose_arg = handler.arguments.iter().nth_back(1).unwrap();
|
|
|
|
let transaction = handler.arguments.last().unwrap();
|
|
|
|
let pattern = match handler.name.as_str() {
|
|
"spend" => TypedPattern::spend_purpose(
|
|
(var_purpose_arg, purpose_arg.location),
|
|
(
|
|
var_datum,
|
|
datum.map(|x| x.location).unwrap_or(Span::empty()),
|
|
),
|
|
redeemer.location,
|
|
),
|
|
"mint" => TypedPattern::mint_purpose(
|
|
(var_purpose_arg, purpose_arg.location),
|
|
redeemer.location,
|
|
),
|
|
"withdraw" => TypedPattern::withdraw_purpose(
|
|
(var_purpose_arg, purpose_arg.location),
|
|
redeemer.location,
|
|
),
|
|
"publish" => TypedPattern::publish_purpose(
|
|
(var_purpose_arg, purpose_arg.location),
|
|
redeemer.location,
|
|
),
|
|
"propose" => TypedPattern::propose_purpose(
|
|
(var_purpose_arg, purpose_arg.location),
|
|
redeemer.location,
|
|
),
|
|
"vote" => TypedPattern::vote_purpose(
|
|
(var_purpose_arg, purpose_arg.location),
|
|
redeemer.location,
|
|
),
|
|
purpose => {
|
|
unreachable!("unexpected/unknown purpose: {:?}", purpose)
|
|
}
|
|
};
|
|
|
|
let mut then = vec![];
|
|
|
|
// expect redeemer: tipo = __redeemer__
|
|
then.push(TypedExpr::flexible_expect(
|
|
TypedExpr::local_var(var_redeemer, Type::data(), redeemer.location),
|
|
TypedPattern::var(redeemer.get_variable_name().unwrap_or("_")),
|
|
redeemer.tipo.clone(),
|
|
redeemer.location,
|
|
));
|
|
|
|
// Cast the datum, if any
|
|
if let Some(datum) = datum {
|
|
// expect datum: tipo = __datum__
|
|
then.push(TypedExpr::flexible_expect(
|
|
TypedExpr::local_var(
|
|
var_datum,
|
|
Type::option(Type::data()),
|
|
datum.location,
|
|
),
|
|
TypedPattern::var(datum.get_variable_name().unwrap_or("_")),
|
|
datum.tipo.clone(),
|
|
datum.location,
|
|
))
|
|
}
|
|
|
|
// let purpose_arg = __purpose_arg__
|
|
if let Some(arg_name) = purpose_arg.get_variable_name() {
|
|
then.push(TypedExpr::let_(
|
|
TypedExpr::local_var(
|
|
var_purpose_arg,
|
|
Type::data(),
|
|
purpose_arg.location,
|
|
),
|
|
TypedPattern::var(arg_name),
|
|
purpose_arg.tipo.clone(),
|
|
purpose_arg.location,
|
|
));
|
|
}
|
|
|
|
// let last_arg_name = __transaction__
|
|
if let Some(arg_name) = transaction.get_variable_name() {
|
|
then.push(TypedExpr::let_(
|
|
TypedExpr::local_var(
|
|
var_transaction,
|
|
Type::data(),
|
|
transaction.location,
|
|
),
|
|
TypedPattern::var(arg_name),
|
|
Type::data(),
|
|
transaction.location,
|
|
));
|
|
}
|
|
|
|
then.push(handler.body.clone());
|
|
|
|
TypedClause {
|
|
location: Span::empty(),
|
|
pattern,
|
|
then: TypedExpr::Sequence {
|
|
location: Span::empty(),
|
|
expressions: then,
|
|
},
|
|
}
|
|
})
|
|
.chain(std::iter::once(&self.fallback).map(|fallback| {
|
|
let arg = fallback.arguments.first().unwrap();
|
|
|
|
let then = match arg.get_variable_name() {
|
|
Some(arg_name) => TypedExpr::sequence(&[
|
|
TypedExpr::let_(
|
|
TypedExpr::local_var(
|
|
var_context,
|
|
arg.tipo.clone(),
|
|
arg.location,
|
|
),
|
|
TypedPattern::var(arg_name),
|
|
arg.tipo.clone(),
|
|
arg.location,
|
|
),
|
|
fallback.body.clone(),
|
|
]),
|
|
None => fallback.body.clone(),
|
|
};
|
|
|
|
TypedClause {
|
|
location: Span::empty(),
|
|
pattern: TypedPattern::Discard {
|
|
name: "_".to_string(),
|
|
location: arg.location,
|
|
},
|
|
then,
|
|
}
|
|
}))
|
|
.collect(),
|
|
},
|
|
])
|
|
}
|
|
|
|
pub fn find_node(&self, byte_index: usize) -> Option<Located<'_>> {
|
|
self.params
|
|
.iter()
|
|
.find_map(|arg| arg.find_node(byte_index))
|
|
.or_else(|| {
|
|
self.handlers
|
|
.iter()
|
|
.find_map(|func| func.find_node(byte_index))
|
|
})
|
|
.or_else(|| self.fallback.find_node(byte_index))
|
|
}
|
|
|
|
pub fn into_function_definitions(
|
|
&self,
|
|
module_name: &str,
|
|
) -> Vec<(FunctionAccessKey, TypedFunction)> {
|
|
self.handlers
|
|
.iter()
|
|
.chain(std::iter::once(&self.fallback))
|
|
.map(|handler| {
|
|
let mut handler = handler.clone();
|
|
|
|
handler.arguments = self
|
|
.params
|
|
.clone()
|
|
.into_iter()
|
|
.chain(handler.arguments)
|
|
.collect();
|
|
|
|
(
|
|
FunctionAccessKey {
|
|
module_name: module_name.to_string(),
|
|
function_name: TypedValidator::handler_name(
|
|
self.name.as_str(),
|
|
handler.name.as_str(),
|
|
),
|
|
},
|
|
handler,
|
|
)
|
|
})
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
pub type TypedDefinition = Definition<Rc<Type>, TypedArg, TypedExpr, String>;
|
|
pub type UntypedDefinition = Definition<(), UntypedArg, UntypedExpr, ()>;
|
|
|
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
pub enum Definition<T, Arg, Expr, PackageName> {
|
|
Fn(Function<T, Expr, Arg>),
|
|
|
|
TypeAlias(TypeAlias<T>),
|
|
|
|
DataType(DataType<T>),
|
|
|
|
Use(Use<PackageName>),
|
|
|
|
ModuleConstant(ModuleConstant<Expr>),
|
|
|
|
Test(Function<T, Expr, ArgVia<Arg, Expr>>),
|
|
|
|
Validator(Validator<T, Arg, Expr>),
|
|
}
|
|
|
|
impl<A, B, C, D> Definition<A, B, C, D> {
|
|
pub fn location(&self) -> Span {
|
|
match self {
|
|
Definition::Fn(Function { location, .. })
|
|
| Definition::Use(Use { location, .. })
|
|
| Definition::TypeAlias(TypeAlias { location, .. })
|
|
| Definition::DataType(DataType { location, .. })
|
|
| Definition::ModuleConstant(ModuleConstant { location, .. })
|
|
| Definition::Validator(Validator { location, .. })
|
|
| Definition::Test(Function { location, .. }) => *location,
|
|
}
|
|
}
|
|
|
|
pub fn put_doc(&mut self, new_doc: String) {
|
|
match self {
|
|
Definition::Use { .. } => (),
|
|
Definition::Fn(Function { doc, .. })
|
|
| Definition::TypeAlias(TypeAlias { doc, .. })
|
|
| Definition::DataType(DataType { doc, .. })
|
|
| Definition::ModuleConstant(ModuleConstant { doc, .. })
|
|
| Definition::Validator(Validator { doc, .. })
|
|
| Definition::Test(Function { doc, .. }) => {
|
|
let _ = std::mem::replace(doc, Some(new_doc));
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn doc(&self) -> Option<String> {
|
|
match self {
|
|
Definition::Use { .. } => None,
|
|
Definition::Fn(Function { doc, .. })
|
|
| Definition::TypeAlias(TypeAlias { doc, .. })
|
|
| Definition::DataType(DataType { doc, .. })
|
|
| Definition::ModuleConstant(ModuleConstant { doc, .. })
|
|
| Definition::Validator(Validator { doc, .. })
|
|
| Definition::Test(Function { doc, .. }) => doc.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TypedDefinition {
|
|
pub fn find_node(&self, byte_index: usize) -> Option<Located<'_>> {
|
|
// Note that the fn span covers the function head, not
|
|
// the entire statement.
|
|
let located = match self {
|
|
Definition::Validator(validator) => validator.find_node(byte_index),
|
|
Definition::Fn(func) => func.find_node(byte_index),
|
|
Definition::Test(func) => func.find_node(byte_index),
|
|
_ => None,
|
|
};
|
|
|
|
if located.is_none() && self.location().contains(byte_index) {
|
|
return Some(Located::Definition(self));
|
|
}
|
|
|
|
located
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub enum Located<'a> {
|
|
Expression(&'a TypedExpr),
|
|
Pattern(&'a TypedPattern, Rc<Type>),
|
|
Definition(&'a TypedDefinition),
|
|
Argument(&'a ArgName, Rc<Type>),
|
|
Annotation(&'a Annotation),
|
|
}
|
|
|
|
impl<'a> Located<'a> {
|
|
pub fn definition_location(&self) -> Option<DefinitionLocation<'_>> {
|
|
match self {
|
|
Self::Expression(expression) => expression.definition_location(),
|
|
Self::Definition(definition) => Some(DefinitionLocation {
|
|
module: None,
|
|
span: definition.location(),
|
|
}),
|
|
// TODO: Revise definition location semantic for 'Pattern'
|
|
// e.g. for constructors, we might want to show the type definition
|
|
// for that constructor.
|
|
Self::Pattern(_, _) | Located::Argument(_, _) | Located::Annotation(_) => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
|
pub struct DefinitionLocation<'module> {
|
|
pub module: Option<&'module str>,
|
|
pub span: Span,
|
|
}
|
|
|
|
pub type TypedCallArg = CallArg<TypedExpr>;
|
|
pub type ParsedCallArg = CallArg<Option<UntypedExpr>>;
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
|
pub struct CallArg<A> {
|
|
pub label: Option<String>,
|
|
pub location: Span,
|
|
pub value: A,
|
|
}
|
|
|
|
impl CallArg<UntypedExpr> {
|
|
pub fn is_capture_hole(&self) -> bool {
|
|
match &self.value {
|
|
UntypedExpr::Var { ref name, .. } => name.contains(CAPTURE_VARIABLE),
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TypedCallArg {
|
|
pub fn find_node(&self, byte_index: usize) -> Option<Located<'_>> {
|
|
self.value.find_node(byte_index)
|
|
}
|
|
}
|
|
|
|
impl CallArg<TypedPattern> {
|
|
pub fn var(name: &str, location: Span) -> Self {
|
|
CallArg {
|
|
label: None,
|
|
location: Span::empty(),
|
|
value: TypedPattern::Var {
|
|
location,
|
|
name: name.to_string(),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
pub struct RecordConstructor<T> {
|
|
pub location: Span,
|
|
pub name: String,
|
|
pub arguments: Vec<RecordConstructorArg<T>>,
|
|
pub doc: Option<String>,
|
|
pub sugar: bool,
|
|
}
|
|
|
|
impl<A> RecordConstructor<A>
|
|
where
|
|
A: Clone,
|
|
{
|
|
pub fn put_doc(&mut self, new_doc: String) {
|
|
self.doc = Some(new_doc);
|
|
}
|
|
|
|
pub fn known_enum(names: &[&str]) -> Vec<RecordConstructor<A>> {
|
|
names
|
|
.iter()
|
|
.map(|name| RecordConstructor {
|
|
location: Span::empty(),
|
|
name: name.to_string(),
|
|
arguments: vec![],
|
|
doc: None,
|
|
sugar: false,
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub fn known_record(name: &str, args: &[RecordConstructorArg<A>]) -> Self {
|
|
RecordConstructor {
|
|
location: Span::empty(),
|
|
name: name.to_string(),
|
|
arguments: args.to_vec(),
|
|
doc: None,
|
|
sugar: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
pub struct RecordConstructorArg<T> {
|
|
pub label: Option<String>,
|
|
// ast
|
|
pub annotation: Annotation,
|
|
pub location: Span,
|
|
pub tipo: T,
|
|
pub doc: Option<String>,
|
|
}
|
|
|
|
impl<T: PartialEq> RecordConstructorArg<T> {
|
|
pub fn put_doc(&mut self, new_doc: String) {
|
|
self.doc = Some(new_doc);
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
pub enum ArgBy {
|
|
ByName(ArgName),
|
|
ByPattern(UntypedPattern),
|
|
}
|
|
|
|
impl ArgBy {
|
|
pub fn into_extra_assignment(
|
|
self,
|
|
name: &ArgName,
|
|
annotation: Option<&Annotation>,
|
|
location: Span,
|
|
) -> Option<UntypedExpr> {
|
|
match self {
|
|
ArgBy::ByName(..) => None,
|
|
ArgBy::ByPattern(pattern) => Some(UntypedExpr::Assignment {
|
|
location,
|
|
value: Box::new(UntypedExpr::Var {
|
|
location,
|
|
name: name.get_name(),
|
|
}),
|
|
patterns: vec1![AssignmentPattern {
|
|
pattern,
|
|
location,
|
|
annotation: annotation.cloned(),
|
|
}],
|
|
kind: AssignmentKind::Let { backpassing: false },
|
|
}),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
pub struct UntypedArg {
|
|
pub by: ArgBy,
|
|
pub location: Span,
|
|
pub annotation: Option<Annotation>,
|
|
pub doc: Option<String>,
|
|
pub is_validator_param: bool,
|
|
}
|
|
|
|
impl UntypedArg {
|
|
pub fn arg_name(&self, ix: usize) -> ArgName {
|
|
match self.by {
|
|
ArgBy::ByName(ref name) => name.clone(),
|
|
ArgBy::ByPattern(..) => {
|
|
// NOTE: We use ordinal here not only because it's cute, but because
|
|
// such a name cannot be parsed to begin with and thus, will not clash
|
|
// with any user-defined name.
|
|
let name = format!("{}{}_arg", ix + 1, Ordinal::<usize>(ix + 1).suffix());
|
|
ArgName::Named {
|
|
label: name.clone(),
|
|
name,
|
|
location: self.location,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn set_type(self, tipo: Rc<Type>, ix: usize) -> TypedArg {
|
|
TypedArg {
|
|
tipo,
|
|
arg_name: self.arg_name(ix),
|
|
location: self.location,
|
|
annotation: self.annotation,
|
|
is_validator_param: self.is_validator_param,
|
|
doc: self.doc,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
pub struct TypedArg {
|
|
pub arg_name: ArgName,
|
|
pub location: Span,
|
|
pub annotation: Option<Annotation>,
|
|
pub doc: Option<String>,
|
|
pub is_validator_param: bool,
|
|
pub tipo: Rc<Type>,
|
|
}
|
|
|
|
impl TypedArg {
|
|
pub fn new(name: &str, tipo: Rc<Type>) -> Self {
|
|
TypedArg {
|
|
arg_name: ArgName::Named {
|
|
name: name.to_string(),
|
|
label: name.to_string(),
|
|
location: Span::empty(),
|
|
},
|
|
location: Span::empty(),
|
|
annotation: None,
|
|
doc: None,
|
|
is_validator_param: false,
|
|
tipo: tipo.clone(),
|
|
}
|
|
}
|
|
|
|
pub fn put_doc(&mut self, new_doc: String) {
|
|
self.doc = Some(new_doc);
|
|
}
|
|
|
|
pub fn get_variable_name(&self) -> Option<&str> {
|
|
self.arg_name.get_variable_name()
|
|
}
|
|
|
|
pub fn get_name(&self) -> String {
|
|
self.arg_name.get_name()
|
|
}
|
|
|
|
pub fn is_capture(&self) -> bool {
|
|
if let ArgName::Named {
|
|
ref name, location, ..
|
|
} = self.arg_name
|
|
{
|
|
return name.starts_with(CAPTURE_VARIABLE)
|
|
&& location == Span::empty()
|
|
&& self.location == Span::empty();
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
pub fn find_node(&self, byte_index: usize) -> Option<Located<'_>> {
|
|
if self.arg_name.location().contains(byte_index) {
|
|
Some(Located::Argument(&self.arg_name, self.tipo.clone()))
|
|
} else {
|
|
self.annotation
|
|
.as_ref()
|
|
.and_then(|annotation| annotation.find_node(byte_index))
|
|
}
|
|
}
|
|
}
|
|
|
|
pub type TypedArgVia = ArgVia<TypedArg, TypedExpr>;
|
|
pub type UntypedArgVia = ArgVia<UntypedArg, UntypedExpr>;
|
|
|
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
pub struct ArgVia<Arg, Expr> {
|
|
pub arg: Arg,
|
|
pub via: Expr,
|
|
}
|
|
|
|
impl<Expr> From<ArgVia<TypedArg, Expr>> for TypedArg {
|
|
fn from(this: ArgVia<TypedArg, Expr>) -> TypedArg {
|
|
this.arg
|
|
}
|
|
}
|
|
|
|
impl<Expr> From<ArgVia<UntypedArg, Expr>> for UntypedArg {
|
|
fn from(this: ArgVia<UntypedArg, Expr>) -> UntypedArg {
|
|
this.arg
|
|
}
|
|
}
|
|
|
|
impl TypedArgVia {
|
|
pub fn find_node(&self, byte_index: usize) -> Option<Located<'_>> {
|
|
if self.arg.arg_name.location().contains(byte_index) {
|
|
Some(Located::Argument(&self.arg.arg_name, self.arg.tipo.clone()))
|
|
} else {
|
|
// `via` is done first here because when there is no manually written
|
|
// annotation, it seems one is injected leading to a `found` returning too early
|
|
// because the span of the filled in annotation matches the span of the via expr.
|
|
self.via.find_node(byte_index).or_else(|| {
|
|
self.arg
|
|
.annotation
|
|
.as_ref()
|
|
.and_then(|annotation| annotation.find_node(byte_index))
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
|
pub enum ArgName {
|
|
Discarded {
|
|
name: String,
|
|
label: String,
|
|
location: Span,
|
|
},
|
|
Named {
|
|
name: String,
|
|
label: String,
|
|
location: Span,
|
|
},
|
|
}
|
|
|
|
impl ArgName {
|
|
pub fn location(&self) -> Span {
|
|
match self {
|
|
ArgName::Discarded { location, .. } => *location,
|
|
ArgName::Named { location, .. } => *location,
|
|
}
|
|
}
|
|
|
|
/// Returns the name of the variable if it is named, otherwise None.
|
|
/// Code gen uses the fact that this returns None to do certain things.
|
|
pub fn get_variable_name(&self) -> Option<&str> {
|
|
match self {
|
|
ArgName::Discarded { .. } => None,
|
|
ArgName::Named { name, .. } => Some(name),
|
|
}
|
|
}
|
|
|
|
pub fn get_name(&self) -> String {
|
|
match self {
|
|
ArgName::Discarded { name, .. } | ArgName::Named { name, .. } => name.clone(),
|
|
}
|
|
}
|
|
|
|
pub fn get_label(&self) -> String {
|
|
match self {
|
|
ArgName::Discarded { label, .. } | ArgName::Named { label, .. } => label.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
|
pub struct UnqualifiedImport {
|
|
pub location: Span,
|
|
pub name: String,
|
|
pub as_name: Option<String>,
|
|
}
|
|
|
|
impl UnqualifiedImport {
|
|
pub fn variable_name(&self) -> &str {
|
|
self.as_name.as_deref().unwrap_or(&self.name)
|
|
}
|
|
}
|
|
|
|
// TypeAst
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
|
pub enum Annotation {
|
|
Constructor {
|
|
location: Span,
|
|
module: Option<String>,
|
|
name: String,
|
|
arguments: Vec<Self>,
|
|
},
|
|
|
|
Fn {
|
|
location: Span,
|
|
arguments: Vec<Self>,
|
|
ret: Box<Self>,
|
|
},
|
|
|
|
Var {
|
|
location: Span,
|
|
name: String,
|
|
},
|
|
|
|
Hole {
|
|
location: Span,
|
|
name: String,
|
|
},
|
|
|
|
Tuple {
|
|
location: Span,
|
|
elems: Vec<Self>,
|
|
},
|
|
|
|
Pair {
|
|
location: Span,
|
|
fst: Box<Self>,
|
|
snd: Box<Self>,
|
|
},
|
|
}
|
|
|
|
impl Annotation {
|
|
pub fn location(&self) -> Span {
|
|
match self {
|
|
Annotation::Fn { location, .. }
|
|
| Annotation::Tuple { location, .. }
|
|
| Annotation::Var { location, .. }
|
|
| Annotation::Hole { location, .. }
|
|
| Annotation::Constructor { location, .. }
|
|
| Annotation::Pair { location, .. } => *location,
|
|
}
|
|
}
|
|
|
|
pub fn boolean(location: Span) -> Self {
|
|
Annotation::Constructor {
|
|
name: "Bool".to_string(),
|
|
module: None,
|
|
arguments: vec![],
|
|
location,
|
|
}
|
|
}
|
|
|
|
pub fn int(location: Span) -> Self {
|
|
Annotation::Constructor {
|
|
name: "Int".to_string(),
|
|
module: None,
|
|
arguments: vec![],
|
|
location,
|
|
}
|
|
}
|
|
|
|
pub fn bytearray(location: Span) -> Self {
|
|
Annotation::Constructor {
|
|
name: "ByteArray".to_string(),
|
|
module: None,
|
|
arguments: vec![],
|
|
location,
|
|
}
|
|
}
|
|
|
|
pub fn data(location: Span) -> Self {
|
|
Annotation::Constructor {
|
|
name: "Data".to_string(),
|
|
module: None,
|
|
arguments: vec![],
|
|
location,
|
|
}
|
|
}
|
|
|
|
pub fn option(inner: Annotation) -> Self {
|
|
Annotation::Constructor {
|
|
name: "Option".to_string(),
|
|
module: None,
|
|
location: inner.location(),
|
|
arguments: vec![inner],
|
|
}
|
|
}
|
|
|
|
pub fn list(inner: Annotation, location: Span) -> Self {
|
|
Annotation::Constructor {
|
|
name: "List".to_string(),
|
|
module: None,
|
|
arguments: vec![inner],
|
|
location,
|
|
}
|
|
}
|
|
|
|
pub fn tuple(elems: Vec<Annotation>, location: Span) -> Self {
|
|
Annotation::Tuple { elems, location }
|
|
}
|
|
|
|
pub fn is_logically_equal(&self, other: &Annotation) -> bool {
|
|
match self {
|
|
Annotation::Constructor {
|
|
module,
|
|
name,
|
|
arguments,
|
|
location: _,
|
|
} => match other {
|
|
Annotation::Constructor {
|
|
module: o_module,
|
|
name: o_name,
|
|
arguments: o_arguments,
|
|
location: _,
|
|
} => {
|
|
module == o_module
|
|
&& name == o_name
|
|
&& arguments.len() == o_arguments.len()
|
|
&& arguments
|
|
.iter()
|
|
.zip(o_arguments)
|
|
.all(|a| a.0.is_logically_equal(a.1))
|
|
}
|
|
_ => false,
|
|
},
|
|
Annotation::Tuple { elems, location: _ } => match other {
|
|
Annotation::Tuple {
|
|
elems: o_elems,
|
|
location: _,
|
|
} => {
|
|
elems.len() == o_elems.len()
|
|
&& elems
|
|
.iter()
|
|
.zip(o_elems)
|
|
.all(|a| a.0.is_logically_equal(a.1))
|
|
}
|
|
_ => false,
|
|
},
|
|
Annotation::Fn {
|
|
arguments,
|
|
ret,
|
|
location: _,
|
|
} => match other {
|
|
Annotation::Fn {
|
|
arguments: o_arguments,
|
|
ret: o_return,
|
|
location: _,
|
|
} => {
|
|
arguments.len() == o_arguments.len()
|
|
&& arguments
|
|
.iter()
|
|
.zip(o_arguments)
|
|
.all(|a| a.0.is_logically_equal(a.1))
|
|
&& ret.is_logically_equal(o_return)
|
|
}
|
|
_ => false,
|
|
},
|
|
Annotation::Var { name, location: _ } => match other {
|
|
Annotation::Var {
|
|
name: o_name,
|
|
location: _,
|
|
} => name == o_name,
|
|
_ => false,
|
|
},
|
|
|
|
Annotation::Hole { name, location: _ } => match other {
|
|
Annotation::Hole {
|
|
name: o_name,
|
|
location: _,
|
|
} => name == o_name,
|
|
_ => false,
|
|
},
|
|
Annotation::Pair { fst, snd, .. } => {
|
|
if let Annotation::Pair {
|
|
fst: o_fst,
|
|
snd: o_snd,
|
|
..
|
|
} = other
|
|
{
|
|
fst.is_logically_equal(o_fst) && snd.is_logically_equal(o_snd)
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn find_node(&self, byte_index: usize) -> Option<Located<'_>> {
|
|
if !self.location().contains(byte_index) {
|
|
return None;
|
|
}
|
|
|
|
let located = match self {
|
|
Annotation::Constructor { arguments, .. } => {
|
|
arguments.iter().find_map(|arg| arg.find_node(byte_index))
|
|
}
|
|
Annotation::Fn { arguments, ret, .. } => arguments
|
|
.iter()
|
|
.find_map(|arg| arg.find_node(byte_index))
|
|
.or_else(|| ret.find_node(byte_index)),
|
|
Annotation::Tuple { elems, .. } => {
|
|
elems.iter().find_map(|arg| arg.find_node(byte_index))
|
|
}
|
|
Annotation::Var { .. } | Annotation::Hole { .. } => None,
|
|
Annotation::Pair { fst, snd, .. } => fst
|
|
.find_node(byte_index)
|
|
.or_else(|| snd.find_node(byte_index)),
|
|
};
|
|
|
|
located.or(Some(Located::Annotation(self)))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
|
pub enum BinOp {
|
|
// Boolean logic
|
|
And,
|
|
Or,
|
|
|
|
// Equality
|
|
Eq,
|
|
NotEq,
|
|
|
|
// Order comparison
|
|
LtInt,
|
|
LtEqInt,
|
|
GtEqInt,
|
|
GtInt,
|
|
|
|
// Maths
|
|
AddInt,
|
|
SubInt,
|
|
MultInt,
|
|
DivInt,
|
|
ModInt,
|
|
}
|
|
|
|
impl From<LogicalOpChainKind> for BinOp {
|
|
fn from(value: LogicalOpChainKind) -> Self {
|
|
match value {
|
|
LogicalOpChainKind::And => BinOp::And,
|
|
LogicalOpChainKind::Or => BinOp::Or,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
|
pub enum UnOp {
|
|
/// !
|
|
Not,
|
|
/// -
|
|
Negate,
|
|
}
|
|
|
|
impl BinOp {
|
|
pub fn precedence(&self) -> u8 {
|
|
// Ensure that this matches the other precedence function for guards
|
|
match self {
|
|
// Pipe is 0
|
|
// Unary operators are 1
|
|
Self::Or => 2,
|
|
|
|
Self::And => 3,
|
|
|
|
Self::Eq | Self::NotEq | Self::LtInt | Self::LtEqInt | Self::GtEqInt | Self::GtInt => 4,
|
|
|
|
// Concatenation operators are typically 5, so we skip it.
|
|
Self::AddInt | Self::SubInt => 6,
|
|
|
|
Self::MultInt | Self::DivInt | Self::ModInt => 7,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub type UntypedPattern = Pattern<(), ()>;
|
|
pub type TypedPattern = Pattern<PatternConstructor, Rc<Type>>;
|
|
|
|
impl TypedPattern {
|
|
pub fn var(name: &str) -> Self {
|
|
TypedPattern::Var {
|
|
name: name.to_string(),
|
|
location: Span::empty(),
|
|
}
|
|
}
|
|
|
|
pub fn constructor(
|
|
name: &str,
|
|
arguments: &[CallArg<TypedPattern>],
|
|
tipo: Rc<Type>,
|
|
location: Span,
|
|
) -> Self {
|
|
TypedPattern::Constructor {
|
|
is_record: false,
|
|
location,
|
|
name: name.to_string(),
|
|
arguments: arguments.to_vec(),
|
|
module: None,
|
|
constructor: PatternConstructor::Record {
|
|
name: name.to_string(),
|
|
field_map: None,
|
|
},
|
|
spread_location: None,
|
|
tipo: tipo.clone(),
|
|
}
|
|
}
|
|
|
|
pub fn mint_purpose(
|
|
(var_purpose_arg, purpose_span): (&str, Span),
|
|
redeemer_span: Span,
|
|
) -> Self {
|
|
TypedPattern::constructor(
|
|
well_known::SCRIPT_PURPOSE_MINT,
|
|
&[CallArg::var(var_purpose_arg, purpose_span)],
|
|
Type::function(vec![Type::byte_array()], Type::script_purpose()),
|
|
redeemer_span,
|
|
)
|
|
}
|
|
|
|
pub fn spend_purpose(
|
|
(var_purpose_arg, purpose_span): (&str, Span),
|
|
(var_datum, datum_span): (&str, Span),
|
|
redeemer_span: Span,
|
|
) -> Self {
|
|
TypedPattern::constructor(
|
|
well_known::SCRIPT_PURPOSE_SPEND,
|
|
&[
|
|
CallArg::var(var_purpose_arg, purpose_span),
|
|
CallArg::var(var_datum, datum_span),
|
|
],
|
|
Type::function(
|
|
vec![Type::data(), Type::option(Type::data())],
|
|
Type::script_purpose(),
|
|
),
|
|
redeemer_span,
|
|
)
|
|
}
|
|
|
|
pub fn withdraw_purpose(
|
|
(var_purpose_arg, purpose_span): (&str, Span),
|
|
redeemer_span: Span,
|
|
) -> Self {
|
|
TypedPattern::constructor(
|
|
well_known::SCRIPT_PURPOSE_WITHDRAW,
|
|
&[CallArg::var(var_purpose_arg, purpose_span)],
|
|
Type::function(vec![Type::data()], Type::script_purpose()),
|
|
redeemer_span,
|
|
)
|
|
}
|
|
|
|
pub fn publish_purpose(
|
|
(var_purpose_arg, purpose_span): (&str, Span),
|
|
redeemer_span: Span,
|
|
) -> Self {
|
|
TypedPattern::constructor(
|
|
well_known::SCRIPT_PURPOSE_PUBLISH,
|
|
&[
|
|
CallArg::var("__discarded_purpose_ix__", purpose_span),
|
|
CallArg::var(var_purpose_arg, purpose_span),
|
|
],
|
|
Type::function(vec![Type::int(), Type::data()], Type::script_purpose()),
|
|
redeemer_span,
|
|
)
|
|
}
|
|
|
|
pub fn vote_purpose(
|
|
(var_purpose_arg, purpose_span): (&str, Span),
|
|
redeemer_span: Span,
|
|
) -> Self {
|
|
TypedPattern::constructor(
|
|
well_known::SCRIPT_PURPOSE_VOTE,
|
|
&[CallArg::var(var_purpose_arg, purpose_span)],
|
|
Type::function(vec![Type::data()], Type::script_purpose()),
|
|
redeemer_span,
|
|
)
|
|
}
|
|
|
|
pub fn propose_purpose(
|
|
(var_purpose_arg, purpose_span): (&str, Span),
|
|
redeemer_span: Span,
|
|
) -> Self {
|
|
TypedPattern::constructor(
|
|
well_known::SCRIPT_PURPOSE_PROPOSE,
|
|
&[
|
|
CallArg::var("__discarded_purpose_ix__", purpose_span),
|
|
CallArg::var(var_purpose_arg, purpose_span),
|
|
],
|
|
Type::function(vec![Type::int(), Type::data()], Type::script_purpose()),
|
|
redeemer_span,
|
|
)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
pub enum Pattern<Constructor, Type> {
|
|
Int {
|
|
location: Span,
|
|
value: String,
|
|
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`
|
|
Var {
|
|
location: Span,
|
|
name: String,
|
|
},
|
|
|
|
/// A name given to a sub-pattern using the `as` keyword.
|
|
///
|
|
/// ```aiken
|
|
/// when foo is {
|
|
/// [_, _] as the_list -> ...
|
|
/// }
|
|
/// ```
|
|
Assign {
|
|
name: String,
|
|
location: Span,
|
|
pattern: Box<Self>,
|
|
},
|
|
|
|
/// A pattern that binds to any value but does not assign a variable.
|
|
/// Always starts with an underscore.
|
|
Discard {
|
|
name: String,
|
|
location: Span,
|
|
},
|
|
|
|
List {
|
|
location: Span,
|
|
elements: Vec<Self>,
|
|
tail: Option<Box<Self>>,
|
|
},
|
|
|
|
/// The constructor for a custom type. Starts with an uppercase letter.
|
|
Constructor {
|
|
is_record: bool,
|
|
location: Span,
|
|
name: String,
|
|
arguments: Vec<CallArg<Self>>,
|
|
module: Option<String>,
|
|
constructor: Constructor,
|
|
spread_location: Option<Span>,
|
|
tipo: Type,
|
|
},
|
|
|
|
Pair {
|
|
location: Span,
|
|
fst: Box<Self>,
|
|
snd: Box<Self>,
|
|
},
|
|
|
|
Tuple {
|
|
location: Span,
|
|
elems: Vec<Self>,
|
|
},
|
|
}
|
|
|
|
impl<A, B> Pattern<A, B> {
|
|
pub fn location(&self) -> Span {
|
|
match self {
|
|
Pattern::Assign { pattern, .. } => pattern.location(),
|
|
Pattern::Int { location, .. }
|
|
| Pattern::Var { location, .. }
|
|
| Pattern::List { location, .. }
|
|
| Pattern::Discard { location, .. }
|
|
| Pattern::Tuple { location, .. }
|
|
| Pattern::Pair { location, .. }
|
|
| Pattern::ByteArray { location, .. }
|
|
| Pattern::Constructor { location, .. } => *location,
|
|
}
|
|
}
|
|
|
|
/// Returns true when a Pattern can be displayed in a flex-break manner (i.e. tries to fit as
|
|
/// much as possible on a single line). When false, long lines with several of those patterns
|
|
/// will be broken down to one pattern per line.
|
|
pub fn is_simple_pattern_to_format(&self) -> bool {
|
|
match self {
|
|
Self::ByteArray { .. } | Self::Int { .. } | Self::Var { .. } | Self::Discard { .. } => {
|
|
true
|
|
}
|
|
Self::Pair { fst, snd, .. } => {
|
|
fst.is_simple_pattern_to_format() && snd.is_simple_pattern_to_format()
|
|
}
|
|
Self::Tuple { elems, .. } => elems.iter().all(|e| e.is_simple_pattern_to_format()),
|
|
Self::List { elements, .. } if elements.len() <= 3 => {
|
|
elements.iter().all(|e| e.is_simple_pattern_to_format())
|
|
}
|
|
Self::Constructor { arguments, .. } => arguments.is_empty(),
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
pub fn with_spread(&self) -> bool {
|
|
match self {
|
|
Pattern::Constructor {
|
|
spread_location, ..
|
|
} => spread_location.is_some(),
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
/// Returns `true` if the pattern is [`Discard`].
|
|
///
|
|
/// [`Discard`]: Pattern::Discard
|
|
pub fn is_discard(&self) -> bool {
|
|
matches!(self, Self::Discard { .. })
|
|
}
|
|
|
|
/// Returns `true` if the pattern is [`Var`].
|
|
///
|
|
/// [`Var`]: Pattern::Discard
|
|
pub fn is_var(&self) -> bool {
|
|
matches!(self, Self::Var { .. })
|
|
}
|
|
}
|
|
|
|
impl UntypedPattern {
|
|
pub fn true_(location: Span) -> UntypedPattern {
|
|
UntypedPattern::Constructor {
|
|
location,
|
|
name: "True".to_string(),
|
|
arguments: vec![],
|
|
constructor: (),
|
|
spread_location: None,
|
|
tipo: (),
|
|
module: None,
|
|
is_record: false,
|
|
}
|
|
}
|
|
|
|
pub fn collect_identifiers<F>(&self, collect: &mut F)
|
|
where
|
|
F: FnMut((String, Span)),
|
|
{
|
|
match self {
|
|
Pattern::Var { name, location } => {
|
|
collect((name.to_string(), *location));
|
|
}
|
|
Pattern::List { elements, .. } => {
|
|
elements.iter().for_each(|e| e.collect_identifiers(collect));
|
|
}
|
|
Pattern::Pair { fst, snd, .. } => {
|
|
fst.collect_identifiers(collect);
|
|
snd.collect_identifiers(collect);
|
|
}
|
|
Pattern::Tuple { elems, .. } => {
|
|
elems.iter().for_each(|e| e.collect_identifiers(collect));
|
|
}
|
|
Pattern::Constructor { arguments, .. } => {
|
|
arguments
|
|
.iter()
|
|
.for_each(|arg| arg.value.collect_identifiers(collect));
|
|
}
|
|
Pattern::Int { .. }
|
|
| Pattern::ByteArray { .. }
|
|
| Pattern::Discard { .. }
|
|
| Pattern::Assign { .. } => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TypedPattern {
|
|
pub fn find_node<'a>(&'a self, byte_index: usize, value: &Rc<Type>) -> Option<Located<'a>> {
|
|
if !self.location().contains(byte_index) {
|
|
return None;
|
|
}
|
|
|
|
match self {
|
|
Pattern::Int { .. }
|
|
| Pattern::Var { .. }
|
|
| Pattern::Assign { .. }
|
|
| Pattern::ByteArray { .. }
|
|
| Pattern::Discard { .. } => Some(Located::Pattern(self, value.clone())),
|
|
|
|
Pattern::List { elements, .. }
|
|
| Pattern::Tuple {
|
|
elems: elements, ..
|
|
} => match &**value {
|
|
Type::Tuple { elems, .. } => elements
|
|
.iter()
|
|
.zip(elems.iter())
|
|
.find_map(|(e, t)| e.find_node(byte_index, t))
|
|
.or(Some(Located::Pattern(self, value.clone()))),
|
|
Type::App {
|
|
module, name, args, ..
|
|
} if module.is_empty() && name == "List" => elements
|
|
.iter()
|
|
// this is the same as above but this uses
|
|
// cycle to repeat the single type arg for a list
|
|
// there's probably a cleaner way to re-use the code
|
|
// from this branch and the above.
|
|
.zip(args.iter().cycle())
|
|
.find_map(|(e, t)| e.find_node(byte_index, t))
|
|
.or(Some(Located::Pattern(self, value.clone()))),
|
|
_ => None,
|
|
},
|
|
|
|
Pattern::Pair { fst, snd, .. } => match &**value {
|
|
Type::Pair {
|
|
fst: fst_v,
|
|
snd: snd_v,
|
|
..
|
|
} => [fst, snd]
|
|
.into_iter()
|
|
.zip([fst_v, snd_v].iter())
|
|
.find_map(|(e, t)| e.find_node(byte_index, t))
|
|
.or(Some(Located::Pattern(self, value.clone()))),
|
|
_ => None,
|
|
},
|
|
|
|
Pattern::Constructor {
|
|
arguments, tipo, ..
|
|
} => match &**tipo {
|
|
Type::Fn { args, .. } => arguments
|
|
.iter()
|
|
.zip(args.iter())
|
|
.find_map(|(e, t)| e.value.find_node(byte_index, t))
|
|
.or(Some(Located::Pattern(self, value.clone()))),
|
|
_ => None,
|
|
},
|
|
}
|
|
}
|
|
|
|
// TODO: This function definition is weird, see where this is used and how.
|
|
pub fn tipo(&self, value: &TypedExpr) -> Option<Rc<Type>> {
|
|
match self {
|
|
Pattern::Int { .. } => Some(Type::int()),
|
|
Pattern::ByteArray { .. } => Some(Type::byte_array()),
|
|
Pattern::Constructor { tipo, .. } => Some(tipo.clone()),
|
|
Pattern::Var { .. } | Pattern::Assign { .. } | Pattern::Discard { .. } => {
|
|
Some(value.tipo())
|
|
}
|
|
Pattern::List { .. } | Pattern::Tuple { .. } | Pattern::Pair { .. } => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Copy, serde::Serialize, serde::Deserialize)]
|
|
pub enum ByteArrayFormatPreference {
|
|
HexadecimalString,
|
|
ArrayOfBytes(Base),
|
|
Utf8String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
|
|
pub enum CurveType {
|
|
Bls12_381(Bls12_381PointType),
|
|
}
|
|
|
|
impl Display for CurveType {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
CurveType::Bls12_381(point) => write!(f, "<Bls12_381, {point}>"),
|
|
}
|
|
}
|
|
}
|
|
impl From<&Curve> for CurveType {
|
|
fn from(value: &Curve) -> Self {
|
|
match value {
|
|
Curve::Bls12_381(point) => CurveType::Bls12_381(point.into()),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
|
|
pub enum Bls12_381PointType {
|
|
G1,
|
|
G2,
|
|
}
|
|
|
|
impl From<&Bls12_381Point> for Bls12_381PointType {
|
|
fn from(value: &Bls12_381Point) -> Self {
|
|
match value {
|
|
Bls12_381Point::G1(_) => Bls12_381PointType::G1,
|
|
Bls12_381Point::G2(_) => Bls12_381PointType::G2,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Display for Bls12_381PointType {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Bls12_381PointType::G1 => write!(f, "G1"),
|
|
Bls12_381PointType::G2 => write!(f, "G2"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Copy, serde::Serialize, serde::Deserialize)]
|
|
pub enum Curve {
|
|
Bls12_381(Bls12_381Point),
|
|
}
|
|
|
|
impl Curve {
|
|
pub fn compress(&self) -> Vec<u8> {
|
|
match self {
|
|
Curve::Bls12_381(point) => match point {
|
|
Bls12_381Point::G1(g1) => g1.compress(),
|
|
Bls12_381Point::G2(g2) => g2.compress(),
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn tipo(&self) -> Rc<Type> {
|
|
match self {
|
|
Curve::Bls12_381(point) => point.tipo(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
|
|
pub enum Bls12_381Point {
|
|
G1(blst::blst_p1),
|
|
G2(blst::blst_p2),
|
|
}
|
|
|
|
impl serde::Serialize for Bls12_381Point {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: serde::Serializer,
|
|
{
|
|
match *self {
|
|
Bls12_381Point::G1(ref p1) => {
|
|
// Assuming `to_bytes` for compression to Vec<u8>
|
|
let bytes = p1.compress();
|
|
|
|
// Serialize as a tuple with a tag for differentiation
|
|
serializer.serialize_newtype_variant("Bls12_381Point", 0, "G1", &bytes)
|
|
}
|
|
Bls12_381Point::G2(ref p2) => {
|
|
let bytes = p2.compress();
|
|
|
|
serializer.serialize_newtype_variant("Bls12_381Point", 1, "G2", &bytes)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'de> serde::Deserialize<'de> for Bls12_381Point {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
enum Field {
|
|
G1,
|
|
G2,
|
|
}
|
|
|
|
impl<'de> serde::Deserialize<'de> for Field {
|
|
fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
struct FieldVisitor;
|
|
|
|
impl<'de> serde::de::Visitor<'de> for FieldVisitor {
|
|
type Value = Field;
|
|
|
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
formatter.write_str("`G1` or `G2`")
|
|
}
|
|
|
|
fn visit_str<E>(self, value: &str) -> Result<Field, E>
|
|
where
|
|
E: serde::de::Error,
|
|
{
|
|
match value {
|
|
"G1" => Ok(Field::G1),
|
|
"G2" => Ok(Field::G2),
|
|
_ => Err(serde::de::Error::unknown_field(value, FIELDS)),
|
|
}
|
|
}
|
|
}
|
|
|
|
deserializer.deserialize_identifier(FieldVisitor)
|
|
}
|
|
}
|
|
|
|
struct Bls12_381PointVisitor;
|
|
|
|
impl<'de> serde::de::Visitor<'de> for Bls12_381PointVisitor {
|
|
type Value = Bls12_381Point;
|
|
|
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
formatter.write_str("struct Bls12_381Point")
|
|
}
|
|
|
|
fn visit_seq<V>(self, mut seq: V) -> Result<Bls12_381Point, V::Error>
|
|
where
|
|
V: serde::de::SeqAccess<'de>,
|
|
{
|
|
let tag = seq
|
|
.next_element::<Field>()?
|
|
.ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
|
|
|
|
let bytes = seq
|
|
.next_element::<Vec<u8>>()?
|
|
.ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;
|
|
|
|
match tag {
|
|
Field::G1 => {
|
|
let p1 =
|
|
blst::blst_p1::uncompress(&bytes).map_err(serde::de::Error::custom)?;
|
|
|
|
Ok(Bls12_381Point::G1(p1))
|
|
}
|
|
Field::G2 => {
|
|
let p2 =
|
|
blst::blst_p2::uncompress(&bytes).map_err(serde::de::Error::custom)?;
|
|
|
|
Ok(Bls12_381Point::G2(p2))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const FIELDS: &[&str] = &["G1", "G2"];
|
|
|
|
deserializer.deserialize_enum("Bls12_381Point", FIELDS, Bls12_381PointVisitor)
|
|
}
|
|
}
|
|
|
|
impl Bls12_381Point {
|
|
pub fn tipo(&self) -> Rc<Type> {
|
|
match self {
|
|
Bls12_381Point::G1(_) => Type::g1_element(),
|
|
Bls12_381Point::G2(_) => Type::g2_element(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for Bls12_381Point {
|
|
fn default() -> Self {
|
|
Bls12_381Point::G1(Default::default())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub struct AssignmentPattern {
|
|
pub pattern: UntypedPattern,
|
|
pub annotation: Option<Annotation>,
|
|
pub location: Span,
|
|
}
|
|
|
|
impl AssignmentPattern {
|
|
pub fn new(
|
|
pattern: UntypedPattern,
|
|
annotation: Option<Annotation>,
|
|
location: Span,
|
|
) -> AssignmentPattern {
|
|
Self {
|
|
pattern,
|
|
annotation,
|
|
location,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<AssignmentPattern> for Vec1<AssignmentPattern> {
|
|
fn from(value: AssignmentPattern) -> Self {
|
|
Vec1::new(value)
|
|
}
|
|
}
|
|
|
|
pub type UntypedAssignmentKind = AssignmentKind<bool>;
|
|
pub type TypedAssignmentKind = AssignmentKind<()>;
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Copy, serde::Serialize, serde::Deserialize)]
|
|
pub enum AssignmentKind<T> {
|
|
Is,
|
|
Let { backpassing: T },
|
|
Expect { backpassing: T },
|
|
}
|
|
|
|
impl From<UntypedAssignmentKind> for TypedAssignmentKind {
|
|
fn from(kind: UntypedAssignmentKind) -> TypedAssignmentKind {
|
|
match kind {
|
|
AssignmentKind::Is => AssignmentKind::Is,
|
|
AssignmentKind::Let { .. } => AssignmentKind::Let { backpassing: () },
|
|
AssignmentKind::Expect { .. } => AssignmentKind::Expect { backpassing: () },
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> AssignmentKind<T> {
|
|
pub fn is_let(&self) -> bool {
|
|
matches!(self, AssignmentKind::Let { .. })
|
|
}
|
|
|
|
pub fn is_expect(&self) -> bool {
|
|
matches!(self, AssignmentKind::Expect { .. })
|
|
}
|
|
|
|
pub fn if_is(&self) -> bool {
|
|
matches!(self, AssignmentKind::Is)
|
|
}
|
|
|
|
pub fn location_offset(&self) -> usize {
|
|
match self {
|
|
AssignmentKind::Is => 2,
|
|
AssignmentKind::Let { .. } => 3,
|
|
AssignmentKind::Expect { .. } => 6,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl AssignmentKind<bool> {
|
|
pub fn is_backpassing(&self) -> bool {
|
|
match self {
|
|
Self::Is => unreachable!(),
|
|
Self::Let { backpassing } | Self::Expect { backpassing } => *backpassing,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: Default> AssignmentKind<T> {
|
|
pub fn let_() -> Self {
|
|
AssignmentKind::Let {
|
|
backpassing: Default::default(),
|
|
}
|
|
}
|
|
|
|
pub fn is() -> Self {
|
|
AssignmentKind::Is
|
|
}
|
|
|
|
pub fn expect() -> Self {
|
|
AssignmentKind::Expect {
|
|
backpassing: Default::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub type MultiPattern<PatternConstructor, Type> = Vec<Pattern<PatternConstructor, Type>>;
|
|
|
|
pub type UntypedMultiPattern = MultiPattern<(), ()>;
|
|
pub type TypedMultiPattern = MultiPattern<PatternConstructor, Rc<Type>>;
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub struct UntypedClause {
|
|
pub location: Span,
|
|
pub patterns: Vec1<Pattern<(), ()>>,
|
|
pub then: UntypedExpr,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
pub struct TypedClause {
|
|
pub location: Span,
|
|
pub pattern: Pattern<PatternConstructor, Rc<Type>>,
|
|
pub then: TypedExpr,
|
|
}
|
|
|
|
impl TypedClause {
|
|
pub fn location(&self) -> Span {
|
|
Span {
|
|
start: self.pattern.location().start,
|
|
end: self.then.location().end,
|
|
}
|
|
}
|
|
|
|
pub fn find_node(&self, byte_index: usize, subject_type: &Rc<Type>) -> Option<Located<'_>> {
|
|
self.pattern
|
|
.find_node(byte_index, subject_type)
|
|
.or_else(|| self.then.find_node(byte_index))
|
|
}
|
|
}
|
|
|
|
pub struct UntypedClauseGuard {}
|
|
|
|
pub type TypedIfBranch = IfBranch<TypedExpr, (TypedPattern, Rc<Type>)>;
|
|
pub type UntypedIfBranch = IfBranch<UntypedExpr, AssignmentPattern>;
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
|
pub struct IfBranch<Expr, Is> {
|
|
pub condition: Expr,
|
|
pub body: Expr,
|
|
pub is: Option<Is>,
|
|
pub location: Span,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
pub struct TypedRecordUpdateArg {
|
|
pub label: String,
|
|
pub location: Span,
|
|
pub value: TypedExpr,
|
|
pub index: usize,
|
|
}
|
|
|
|
impl TypedRecordUpdateArg {
|
|
pub fn find_node(&self, byte_index: usize) -> Option<Located<'_>> {
|
|
self.value.find_node(byte_index)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub struct UntypedRecordUpdateArg {
|
|
pub label: String,
|
|
pub location: Span,
|
|
pub value: UntypedExpr,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub struct RecordUpdateSpread {
|
|
pub base: Box<UntypedExpr>,
|
|
pub location: Span,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum TraceKind {
|
|
Trace,
|
|
Todo,
|
|
Error,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum Tracing {
|
|
UserDefined(TraceLevel),
|
|
CompilerGenerated(TraceLevel),
|
|
All(TraceLevel),
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum TraceLevel {
|
|
Silent, // No traces
|
|
Compact, // Line numbers only
|
|
Verbose, // Full verbose traces as provided by the user or the compiler
|
|
}
|
|
|
|
impl Tracing {
|
|
pub fn verbose() -> Self {
|
|
Tracing::All(TraceLevel::Verbose)
|
|
}
|
|
|
|
pub fn silent() -> Self {
|
|
Tracing::All(TraceLevel::Silent)
|
|
}
|
|
|
|
/// Get the tracing level based on the context we're in.
|
|
pub fn trace_level(&self, is_code_gen: bool) -> TraceLevel {
|
|
match self {
|
|
Tracing::UserDefined(lvl) => {
|
|
if is_code_gen {
|
|
TraceLevel::Silent
|
|
} else {
|
|
*lvl
|
|
}
|
|
}
|
|
Tracing::CompilerGenerated(lvl) => {
|
|
if is_code_gen {
|
|
*lvl
|
|
} else {
|
|
TraceLevel::Silent
|
|
}
|
|
}
|
|
Tracing::All(lvl) => *lvl,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Display for TraceLevel {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
|
|
match self {
|
|
TraceLevel::Silent => f.write_str("silent"),
|
|
TraceLevel::Compact => f.write_str("compact"),
|
|
TraceLevel::Verbose => f.write_str("verbose"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
|
pub struct Span {
|
|
pub start: usize,
|
|
pub end: usize,
|
|
}
|
|
|
|
impl From<Span> for miette::SourceSpan {
|
|
fn from(span: Span) -> Self {
|
|
Self::new(span.start.into(), span.end - span.start)
|
|
}
|
|
}
|
|
|
|
impl Span {
|
|
pub fn empty() -> Self {
|
|
use chumsky::Span;
|
|
|
|
Self::new((), 0..0)
|
|
}
|
|
|
|
pub fn create(i: usize, n: usize) -> Self {
|
|
use chumsky::Span;
|
|
|
|
Self::new((), i..i + n)
|
|
}
|
|
|
|
pub fn range(&self) -> Range<usize> {
|
|
use chumsky::Span;
|
|
|
|
self.start()..self.end()
|
|
}
|
|
|
|
pub fn union(self, other: Self) -> Self {
|
|
use chumsky::Span;
|
|
|
|
Self {
|
|
start: self.start().min(other.start()),
|
|
end: self.end().max(other.end()),
|
|
}
|
|
}
|
|
|
|
/// Map the current start and end of the Span to new values.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use aiken_lang::ast::Span;
|
|
///
|
|
/// let span = Span { start: 0, end: 1 };
|
|
///
|
|
/// let other = span.map(|start, end| (start + 2, end + 4));
|
|
///
|
|
/// assert_eq!(other.start, 2);
|
|
/// assert_eq!(other.end, 5);
|
|
/// ```
|
|
pub fn map<F: FnOnce(usize, usize) -> (usize, usize)>(&self, f: F) -> Self {
|
|
let (start, end) = f(self.start, self.end);
|
|
|
|
Self { start, end }
|
|
}
|
|
|
|
/// Map the current end of the Span to a new value.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use aiken_lang::ast::Span;
|
|
///
|
|
/// let span = Span { start: 0, end: 1 };
|
|
///
|
|
/// let other = span.map_end(|end| end + 1);
|
|
///
|
|
/// assert_eq!(other.start, 0);
|
|
/// assert_eq!(other.end, 2);
|
|
/// ```
|
|
pub fn map_end<F: FnOnce(usize) -> usize>(&self, f: F) -> Self {
|
|
Self {
|
|
start: self.start,
|
|
end: f(self.end),
|
|
}
|
|
}
|
|
|
|
pub fn contains(&self, byte_index: usize) -> bool {
|
|
byte_index >= self.start && byte_index < self.end
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for Span {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "{:?}", self.range())
|
|
}
|
|
}
|
|
|
|
impl chumsky::Span for Span {
|
|
type Context = ();
|
|
|
|
type Offset = usize;
|
|
|
|
fn new(_context: Self::Context, range: Range<Self::Offset>) -> Self {
|
|
assert!(range.start <= range.end);
|
|
|
|
Self {
|
|
start: range.start,
|
|
end: range.end,
|
|
}
|
|
}
|
|
|
|
fn context(&self) -> Self::Context {}
|
|
|
|
fn start(&self) -> Self::Offset {
|
|
self.start
|
|
}
|
|
|
|
fn end(&self) -> Self::Offset {
|
|
self.end
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum LogicalOpChainKind {
|
|
And,
|
|
Or,
|
|
}
|
|
|
|
impl Display for LogicalOpChainKind {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
LogicalOpChainKind::And => write!(f, "and"),
|
|
LogicalOpChainKind::Or => write!(f, "or"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, thiserror::Error, Diagnostic)]
|
|
pub enum Error {
|
|
#[error(
|
|
"I realized the module '{}' contains the keyword '{}', which is forbidden.\n",
|
|
name.if_supports_color(Stdout, |s| s.purple()),
|
|
keyword.if_supports_color(Stdout, |s| s.purple())
|
|
)]
|
|
#[diagnostic(url("https://aiken-lang.org/language-tour/modules"))]
|
|
#[diagnostic(code("illegal::module_name"))]
|
|
#[diagnostic(help(r#"You cannot use keywords as part of a module path name. As a quick reminder, here's a list of all the keywords (and thus, of invalid module path names):
|
|
|
|
as, expect, check, const, else, fn, if, is, let, opaque, pub, test, todo, trace, type, use, when"#))]
|
|
KeywordInModuleName { name: String, keyword: String },
|
|
|
|
#[error("I realized you used '{}' as a module name, which is reserved (and not available).\n",
|
|
name.if_supports_color(Stdout, |s| s.purple())
|
|
)]
|
|
#[diagnostic(code("illegal::module_name"))]
|
|
#[diagnostic(help(r#"Some module names are reserved for internal use. This the case of:
|
|
|
|
- aiken: where the prelude is located;
|
|
- aiken/builtin: where I store low-level Plutus builtins.
|
|
|
|
Note that 'aiken' is also imported by default; but you can refer to it explicitly to disambiguate with a local value that would clash with one from that module."#
|
|
))]
|
|
ReservedModuleName { name: String },
|
|
}
|