use std::{fmt, ops::Range, sync::Arc}; use crate::{ builtins::{self, bool}, expr::{TypedExpr, UntypedExpr}, tipo::{PatternConstructor, Type, TypeInfo}, }; pub const ASSERT_VARIABLE: &str = "_try"; pub const CAPTURE_VARIABLE: &str = "_capture"; pub const PIPE_VARIABLE: &str = "_pipe"; pub const TRY_VARIABLE: &str = "_try"; pub type TypedModule = Module; pub type UntypedModule = Module<(), UntypedDefinition>; #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum ModuleKind { Lib, Validator, } impl ModuleKind { pub fn is_validator(&self) -> bool { matches!(self, ModuleKind::Validator) } pub fn is_lib(&self) -> bool { matches!(self, ModuleKind::Lib) } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct Module { pub name: String, pub docs: Vec, pub type_info: Info, pub definitions: Vec, pub kind: ModuleKind, } impl Module { pub fn definitions(&self) -> impl Iterator { self.definitions.iter() } pub fn into_definitions(self) -> impl Iterator { self.definitions.into_iter() } } impl UntypedModule { pub fn dependencies(&self) -> Vec<(String, Span)> { self.definitions() .flat_map(|def| { if let Definition::Use(Use { location, module, .. }) = def { Some((module.join("/"), *location)) } else { None } }) .collect() } } pub type TypedFunction = Function, TypedExpr>; pub type UntypedFunction = Function<(), UntypedExpr>; #[derive(Debug, Clone, PartialEq)] pub struct Function { pub arguments: Vec>, pub body: Expr, pub doc: Option, pub location: Span, pub name: String, pub public: bool, pub return_annotation: Option, pub return_type: T, pub end_position: usize, } pub type TypedTypeAlias = TypeAlias>; pub type UntypedTypeAlias = TypeAlias<()>; impl TypedFunction { pub fn test_hint(&self) -> Option<(BinOp, Box, Box)> { do_test_hint(&self.body) } } pub fn do_test_hint(body: &TypedExpr) -> Option<(BinOp, Box, Box)> { match body { TypedExpr::BinOp { name, tipo, left, right, .. } if tipo == &bool() => Some((*name, left.clone(), right.clone())), TypedExpr::Sequence { expressions, .. } | TypedExpr::Pipeline { expressions, .. } => { if let Some((binop, left, right)) = do_test_hint(&expressions[expressions.len() - 1]) { let mut new_left_expressions = expressions.clone(); new_left_expressions.pop(); new_left_expressions.push(*left); let mut new_right_expressions = expressions.clone(); new_right_expressions.pop(); new_right_expressions.push(*right); Some(( binop, TypedExpr::Sequence { expressions: new_left_expressions, location: Span::empty(), } .into(), TypedExpr::Sequence { expressions: new_right_expressions, location: Span::empty(), } .into(), )) } else { None } } _ => None, } } #[derive(Debug, Clone, PartialEq)] pub struct TypeAlias { pub alias: String, pub annotation: Annotation, pub doc: Option, pub location: Span, pub parameters: Vec, pub public: bool, pub tipo: T, } pub type TypedDataType = DataType>; impl TypedDataType { pub fn ordering() -> Self { DataType { constructors: vec![ RecordConstructor { location: Span::empty(), name: "Less".to_string(), arguments: vec![], doc: None, sugar: false, }, RecordConstructor { location: Span::empty(), name: "Equal".to_string(), arguments: vec![], doc: None, sugar: false, }, RecordConstructor { location: Span::empty(), name: "Greater".to_string(), arguments: vec![], doc: None, sugar: false, }, ], doc: None, location: Span::empty(), name: "Ordering".to_string(), opaque: false, parameters: vec![], public: true, typed_parameters: vec![], } } pub fn option(tipo: Arc) -> Self { DataType { constructors: vec![ RecordConstructor { location: Span::empty(), name: "Some".to_string(), arguments: vec![RecordConstructorArg { label: None, annotation: Annotation::Var { location: Span::empty(), name: "a".to_string(), }, location: Span::empty(), tipo: tipo.clone(), doc: None, }], doc: None, sugar: false, }, RecordConstructor { location: Span::empty(), name: "None".to_string(), arguments: vec![], doc: None, sugar: false, }, ], doc: None, location: Span::empty(), name: "Option".to_string(), opaque: false, parameters: vec!["a".to_string()], public: true, typed_parameters: vec![tipo], } } } pub type UntypedDataType = DataType<()>; #[derive(Debug, Clone, PartialEq)] pub struct DataType { pub constructors: Vec>, pub doc: Option, pub location: Span, pub name: String, pub opaque: bool, pub parameters: Vec, pub public: bool, pub typed_parameters: Vec, } pub type TypedUse = Use; pub type UntypedUse = Use<()>; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Use { pub as_name: Option, pub location: Span, pub module: Vec, pub package: PackageName, pub unqualified: Vec, } pub type TypedModuleConstant = ModuleConstant>; pub type UntypedModuleConstant = ModuleConstant<()>; #[derive(Debug, Clone, PartialEq)] pub struct ModuleConstant { pub doc: Option, pub location: Span, pub public: bool, pub name: String, pub annotation: Option, pub value: Box, pub tipo: T, } pub type TypedValidator = Validator, TypedExpr>; pub type UntypedValidator = Validator<(), UntypedExpr>; #[derive(Debug, Clone, PartialEq)] pub struct Validator { pub doc: Option, pub end_position: usize, pub fun: Function, pub location: Span, pub params: Vec>, } pub type TypedDefinition = Definition, TypedExpr, String>; pub type UntypedDefinition = Definition<(), UntypedExpr, ()>; #[derive(Debug, Clone, PartialEq)] pub enum Definition { Fn(Function), TypeAlias(TypeAlias), DataType(DataType), Use(Use), ModuleConstant(ModuleConstant), Test(Function), Validator(Validator), } impl Definition { 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)); } } } } #[derive(Debug, PartialEq, Eq, Clone)] pub struct DefinitionLocation<'module> { pub module: Option<&'module str>, pub span: Span, } #[derive(Debug, Clone, PartialEq)] pub enum Constant { Int { location: Span, value: String, }, String { location: Span, value: String, }, ByteArray { location: Span, bytes: Vec, preferred_format: ByteArrayFormatPreference, }, } impl Constant { pub fn tipo(&self) -> Arc { match self { Constant::Int { .. } => builtins::int(), Constant::String { .. } => builtins::string(), Constant::ByteArray { .. } => builtins::byte_array(), } } pub fn location(&self) -> Span { match self { Constant::Int { location, .. } | Constant::String { location, .. } | Constant::ByteArray { location, .. } => *location, } } } pub type TypedCallArg = CallArg; #[derive(Debug, Clone, PartialEq, Eq)] pub struct CallArg { pub label: Option, pub location: Span, pub value: A, } impl CallArg { pub fn is_capture_hole(&self) -> bool { match &self.value { UntypedExpr::Var { ref name, .. } => name.contains(CAPTURE_VARIABLE), _ => false, } } } #[derive(Debug, Clone, PartialEq)] pub struct RecordConstructor { pub location: Span, pub name: String, pub arguments: Vec>, pub doc: Option, pub sugar: bool, } impl RecordConstructor { pub fn put_doc(&mut self, new_doc: String) { self.doc = Some(new_doc); } } #[derive(Debug, Clone, PartialEq)] pub struct RecordConstructorArg { pub label: Option, // ast pub annotation: Annotation, pub location: Span, pub tipo: T, pub doc: Option, } impl RecordConstructorArg { pub fn put_doc(&mut self, new_doc: String) { self.doc = Some(new_doc); } } pub type TypedArg = Arg>; pub type UntypedArg = Arg<()>; #[derive(Debug, Clone, PartialEq)] pub struct Arg { pub arg_name: ArgName, pub location: Span, pub annotation: Option, pub tipo: T, } impl Arg { pub fn set_type(self, tipo: B) -> Arg { Arg { tipo, arg_name: self.arg_name, location: self.location, annotation: self.annotation, } } pub fn get_variable_name(&self) -> Option<&str> { self.arg_name.get_variable_name() } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ArgName { Discarded { name: String, label: String, location: Span, }, Named { name: String, label: String, location: Span, }, } impl ArgName { pub fn get_variable_name(&self) -> Option<&str> { match self { ArgName::Discarded { .. } => None, ArgName::Named { name, .. } => Some(name), } } pub fn get_label(&self) -> String { match self { ArgName::Discarded { label, .. } => label.to_string(), ArgName::Named { label, .. } => label.to_string(), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct UnqualifiedImport { pub location: Span, pub name: String, pub as_name: Option, pub layer: Layer, } impl UnqualifiedImport { pub fn variable_name(&self) -> &str { self.as_name.as_deref().unwrap_or(&self.name) } pub fn is_value(&self) -> bool { self.layer.is_value() } } // TypeAst #[derive(Debug, Clone, PartialEq)] pub enum Annotation { Constructor { location: Span, module: Option, name: String, arguments: Vec, }, Fn { location: Span, arguments: Vec, ret: Box, }, Var { location: Span, name: String, }, Hole { location: Span, name: String, }, Tuple { location: Span, elems: Vec, }, } impl Annotation { pub fn location(&self) -> Span { match self { Annotation::Fn { location, .. } | Annotation::Tuple { location, .. } | Annotation::Var { location, .. } | Annotation::Hole { location, .. } | Annotation::Constructor { location, .. } => *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, }, } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum Layer { Value, Type, } impl Default for Layer { fn default() -> Self { Layer::Value } } impl Layer { /// Returns `true` if the layer is [`Value`]. pub fn is_value(&self) -> bool { matches!(self, Self::Value) } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BinOp { // Boolean logic And, Or, // Equality Eq, NotEq, // Order comparison LtInt, LtEqInt, GtEqInt, GtInt, // Maths AddInt, SubInt, MultInt, DivInt, ModInt, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum UnOp { // ! Not, // - Negate, } impl BinOp { pub fn precedence(&self) -> u8 { // Ensure that this matches the other precedence function for guards match self { Self::Or => 1, Self::And => 2, Self::Eq | Self::NotEq => 3, Self::LtInt | Self::LtEqInt | Self::GtEqInt | Self::GtInt => 4, // Pipe is 5 Self::AddInt | Self::SubInt => 6, Self::MultInt | Self::DivInt | Self::ModInt => 7, } } } pub type UntypedPattern = Pattern<(), ()>; pub type TypedPattern = Pattern>; #[derive(Debug, Clone, PartialEq)] pub enum Pattern { Int { location: Span, value: String, }, /// The creation of a variable. /// e.g. `assert [this_is_a_var, .._] = x` Var { location: Span, name: String, }, /// A name given to a sub-pattern using the `as` keyword. /// e.g. `assert (1, [_, _] as the_list) = x` Assign { name: String, location: Span, pattern: Box, }, /// 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, tail: Option>, }, /// The constructor for a custom type. Starts with an uppercase letter. Constructor { is_record: bool, location: Span, name: String, arguments: Vec>, module: Option, constructor: Constructor, with_spread: bool, tipo: Type, }, Tuple { location: Span, elems: Vec, }, } impl Pattern { 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::Concatenate { location, .. } | Pattern::Constructor { location, .. } => *location, } } /// 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 { .. }) } } #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub enum ByteArrayFormatPreference { HexadecimalString, ArrayOfBytes, Utf8String, } #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub enum AssignmentKind { Let, Expect, } impl AssignmentKind { pub fn is_let(&self) -> bool { matches!(self, AssignmentKind::Let) } pub fn is_expect(&self) -> bool { matches!(self, AssignmentKind::Expect) } pub fn location_offset(&self) -> usize { match self { AssignmentKind::Let => 3, AssignmentKind::Expect => 6, } } } pub type MultiPattern = Vec>; pub type UntypedMultiPattern = MultiPattern<(), ()>; pub type TypedMultiPattern = MultiPattern>; pub type TypedClause = Clause>; pub type UntypedClause = Clause; #[derive(Debug, Clone, PartialEq)] pub struct Clause { pub location: Span, pub pattern: MultiPattern, pub alternative_patterns: Vec>, pub guard: Option>, pub then: Expr, } impl TypedClause { pub fn location(&self) -> Span { Span { start: self .pattern .get(0) .map(|p| p.location().start) .unwrap_or_default(), end: self.then.location().end, } } } pub type UntypedClauseGuard = ClauseGuard<()>; pub type TypedClauseGuard = ClauseGuard>; #[derive(Debug, Clone, PartialEq)] pub enum ClauseGuard { Not { location: Span, value: Box, }, Equals { location: Span, left: Box, right: Box, }, NotEquals { location: Span, left: Box, right: Box, }, GtInt { location: Span, left: Box, right: Box, }, GtEqInt { location: Span, left: Box, right: Box, }, LtInt { location: Span, left: Box, right: Box, }, LtEqInt { location: Span, left: Box, right: Box, }, Or { location: Span, left: Box, right: Box, }, And { location: Span, left: Box, right: Box, }, Var { location: Span, tipo: Type, name: String, }, Constant(Constant), } impl ClauseGuard { pub fn location(&self) -> Span { match self { ClauseGuard::Constant(constant) => constant.location(), ClauseGuard::Not { location, .. } | ClauseGuard::Or { location, .. } | ClauseGuard::And { location, .. } | ClauseGuard::Var { location, .. } | ClauseGuard::Equals { location, .. } | ClauseGuard::NotEquals { location, .. } | ClauseGuard::GtInt { location, .. } | ClauseGuard::GtEqInt { location, .. } | ClauseGuard::LtInt { location, .. } | ClauseGuard::LtEqInt { location, .. } => *location, } } pub fn precedence(&self) -> u8 { // Ensure that this matches the other precedence function for guards match self { ClauseGuard::Not { .. } => 1, ClauseGuard::Or { .. } => 2, ClauseGuard::And { .. } => 3, ClauseGuard::Equals { .. } | ClauseGuard::NotEquals { .. } => 4, ClauseGuard::GtInt { .. } | ClauseGuard::GtEqInt { .. } | ClauseGuard::LtInt { .. } | ClauseGuard::LtEqInt { .. } => 5, ClauseGuard::Constant(_) | ClauseGuard::Var { .. } => 6, } } } impl TypedClauseGuard { pub fn tipo(&self) -> Arc { match self { ClauseGuard::Var { tipo, .. } => tipo.clone(), ClauseGuard::Constant(constant) => constant.tipo(), ClauseGuard::Not { .. } | ClauseGuard::Or { .. } | ClauseGuard::And { .. } | ClauseGuard::Equals { .. } | ClauseGuard::NotEquals { .. } | ClauseGuard::GtInt { .. } | ClauseGuard::GtEqInt { .. } | ClauseGuard::LtInt { .. } | ClauseGuard::LtEqInt { .. } => bool(), } } } pub type TypedIfBranch = IfBranch; pub type UntypedIfBranch = IfBranch; #[derive(Debug, Clone, PartialEq, Eq)] pub struct IfBranch { pub condition: Expr, pub body: Expr, pub location: Span, } #[derive(Debug, Clone)] pub struct TypedRecordUpdateArg { pub label: String, pub location: Span, pub value: TypedExpr, pub index: usize, } #[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, pub location: Span, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum TraceKind { Trace, Todo, Error, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Tracing { NoTraces, KeepTraces, } impl From for Tracing { fn from(keep: bool) -> Self { if keep { Tracing::KeepTraces } else { Tracing::NoTraces } } } #[derive(Copy, Clone, PartialEq, Eq)] pub struct Span { pub start: usize, pub end: usize, } impl From for miette::SourceSpan { fn from(span: Span) -> Self { Self::new(span.start.into(), (span.end - span.start).into()) } } impl Span { pub fn empty() -> Self { use chumsky::Span; Self::new((), 0..0) } pub fn range(&self) -> Range { 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()), } } } 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 { 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 } }