//! The Abstract Syntax Tree defines an interface between the parser and type checker use crate::span::Span; pub mod macro_matcher; pub mod visit; pub mod fold; mod display; /// An annotation: extra data added on to important AST nodes. pub trait Annotation: Clone + std::fmt::Display + std::fmt::Debug + PartialEq + Eq {} impl Annotation for T {} /// A value with an annotation. #[derive(Clone, PartialEq, Eq)] pub struct Anno(pub T, pub A); /// Expressions: The beating heart of Dough. /// /// A program in Doughlang is a single expression which, at compile time, /// sets up the state in which a program will run. This expression binds types, /// functions, and values to names which are exposed at runtime. /// /// Whereas in the body of a function, `do` sequences are ordered, in the global /// scope (or subsequent module scopes, which are children of the global module,) /// `do` sequences are considered unordered, and subexpressions may be reordered /// in whichever way the compiler sees fit. This is especially important when /// performing import resolution, as imports typically depend on the order /// in which names are bound. #[derive(Clone, Debug, PartialEq, Eq)] pub enum Expr { /// Omitted by semicolon insertion-elision rules Omitted, /// An identifier Id(Path), /// An escaped token for macro binding MetId(String), /// A literal bool, string, char, or int Lit(Literal), /// use Use Use(Use), /// `let Pat::NoTopAlt (= expr (else expr)?)?` | /// `(fn | mod | impl) Pat::Fn Expr` Bind(Box>), /// Expr { (Ident (: Expr)?),* } Make(Box>), /// Op Expr | Expr Op | Expr (Op Expr)+ | Op Expr Expr else Expr Op(Op, Vec>), } /// Doughlang's AST is partitioned by data representation, so it /// considers any expression which is composed solely of keywords, /// symbols, and other expressions as operator expressions. /// /// This includes: /// - Do-sequence expressions: `Expr ; Expr ` /// - Type-cast expressions `Expr as Expr` /// - Binding-modifier expressions: `pub Expr`, `#[Expr] Expr` /// - Block and Group expressions: `{Expr?}`, `(Expr?)` /// - Control flow: `if`, `while`, `loop`, `match`, `break`, `return` /// - Function calls `Expr (Expr,*)` /// - Traditional binary and unary operators (add, sub, neg, assign) #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Op { /// `Expr (; Expr)*` Do, /// `Expr as Expr` As, /// `macro { (Pat => Expr)* }` Macro, /// `{ Expr }` Block, /// `[ Expr,* ]` Array, /// `[ Expr ; Expr ]` ArRep, /// `( Expr )` Group, /// `Expr (, Expr)*` Tuple, /// `#[ Expr ]` Meta, /// `Expr '?'` Try, /// `Expr [ Expr,* ]` Index, /// `Expr ( Expr,* )` Call, /// `pub Expr` Pub, /// `const Expr` Const, /// `static Expr` Static, /// `loop Expr` Loop, /// `match Expr { ,* }` Match, /// `if Expr Expr (else Expr)?` If, /// `while Expr Expr (else Expr)?` While, /// `break Expr` Break, /// `return Expr` Return, /// `continue` Continue, /// `Expr . Expr` Dot, /// `Expr? ..Expr` RangeEx, /// `Expr? ..=Expr` RangeIn, /// `-Expr` Neg, /// `!Expr` Not, /// `!!Expr` Identity, /// `&Expr` Refer, /// `*Expr` Deref, /// `Expr * Expr` Mul, /// `Expr / Expr` Div, /// `Expr % Expr` Rem, /// `Expr + Expr` Add, /// `Expr - Expr` Sub, /// `Expr << Expr` Shl, /// `Expr >> Expr` Shr, /// `Expr & Expr` And, /// `Expr ^ Expr` Xor, /// `Expr | Expr` Or, /// `Expr < Expr` Lt, /// `Expr <= Expr` Leq, /// `Expr == Expr` Eq, /// `Expr != Expr` Neq, /// `Expr >= Expr` Geq, /// `Expr > Expr` Gt, /// `Expr && Expr` LogAnd, /// `Expr ^^ Expr` LogXor, /// `Expr || Expr` LogOr, /// `Expr = Expr` Set, /// `Expr *= Expr` MulSet, /// `Expr /= Expr` DivSet, /// `Expr %= Expr` RemSet, /// `Expr += Expr` AddSet, /// `Expr -= Expr` SubSet, /// `Expr <<= Expr` ShlSet, /// `Expr >>= Expr` ShrSet, /// `Expr &= Expr` AndSet, /// `Expr ^= Expr` XorSet, /// `Expr |= Expr` OrSet, } /// A qualified identifier /// /// TODO: qualify identifier #[derive(Clone, Debug, PartialEq, Eq)] pub struct Path { // TODO: Identifier interning pub parts: Vec, // TODO: generic parameters } /// A literal value (boolean, character, integer, string) #[derive(Clone, Debug, PartialEq, Eq)] pub enum Literal { /// A boolean literal: true | false Bool(bool), /// A character literal: 'a', '\u{1f988}' Char(char), /// An integer literal: 0, 123, 0x10 Int(u128, u32), /// A string literal: Str(String), } /// A compound import declaration #[derive(Clone, Debug, PartialEq, Eq)] pub enum Use { /// "*" Glob, /// Identifier Name(String), /// Identifier as Identifier Alias(String, String), /// Identifier :: Use Path(String, Box), /// { Use, * } Tree(Vec), } /// A pattern binding /// ```ignore /// let Pat (= Expr (else Expr)?)? /// type Pat (= Expr)? /// fn Pat Expr /// mod Pat Expr /// impl Pat Expr /// struct Pat /// enum Pat /// for Pat in Expr Expr (else Expr)? /// Pat => Expr // in match /// ``` #[derive(Clone, Debug, PartialEq, Eq)] pub struct Bind( pub BindOp, pub Vec, pub Pat, pub Vec, A>>, ); #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum BindOp { /// A `let Pat (= Expr (else Expr)?)?` binding Let, /// A type-alias binding Type, /// A `fn Pat Expr` binding Fn, /// A `mod Pat Expr` binding Mod, /// An `impl Pat Expr` binding Impl, /// A struct definition Struct, /// An enum definition Enum, /// A `for Pat in Expr Expr (else Expr)?` binding For, /// A `Pat => Expr` binding Match, } /// A make (constructor) expression /// ```ignore /// Expr { (Ident (: Expr)?),* } /// ``` #[derive(Clone, Debug, PartialEq, Eq)] pub struct Make(pub Anno, A>, pub Vec>); /// A single "arm" of a make expression /// ```text /// Identifier (':' Expr)? /// ``` #[derive(Clone, Debug, PartialEq, Eq)] pub struct MakeArm(pub String, pub Option, A>>); /// Binding patterns for each kind of matchable value. /// /// This covers both patterns in Match expressions, and type annotations. #[derive(Clone, Debug, PartialEq, Eq)] pub enum Pat { /// Matches anything without binding Ignore, /// Matches nothing, ever Never, /// Matches nothing; used for macro substitution MetId(String), /// Matches anything, and binds it to a name Name(String), /// Matches a value by equality comparison Value(Box, A>>), /// Matches a compound pattern Op(PatOp, Vec>), } /// Operators on lists of patterns #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum PatOp { /// Changes the visibility mode to "public" Pub, /// Changes the binding mode to "mutable" Mut, /// Matches the dereference of a pointer (`&pat`) Ref, /// Matches the dereference of a raw pointer (`*pat`) Ptr, /// Matches a partial decomposition (`..rest`) or upper-bounded range (`..100`) Rest, /// Matches an exclusive bounded range (`0..100`) RangeEx, /// Matches an inclusive bounded range (`0..=100`) RangeIn, /// Matches the elements of a record or struct { a, b, c } Record, /// Matches the elements of a tuple ( a, b, c ) Tuple, /// Matches the elements of a slice or array [ a, b, c ] Slice, /// Matches a constant-size slice with repeating elements ArRep, /// Matches a type annotation or struct member Typed, /// Matches a prefix-type-annotated structure TypePrefixed, /// Matches a generic specialization annotation Generic, /// Changes the binding mode to "function-body" Fn, /// Matches one of a list of alternatives Alt, } impl std::fmt::Debug for Anno { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ::fmt(&self.1, f)?; f.write_str(": ")?; ::fmt(&self.0, f) } } impl Default for Expr { fn default() -> Self { Self::Op(Op::Tuple, vec![]) } } impl Expr { pub const fn anno(self, annotation: A) -> Anno, A> { Anno(self, annotation) } pub fn and_do(self, annotation: A, other: Anno, A>) -> Self { let Self::Op(Op::Do, mut exprs) = self else { return Self::Op(Op::Do, vec![self.anno(annotation), other]); }; let Anno(Self::Op(Op::Do, mut other), _) = other else { exprs.push(other); return Self::Op(Op::Do, exprs); }; exprs.append(&mut other); Self::Op(Op::Do, exprs) } pub fn to_tuple(self, annotation: A) -> Self { match self { Self::Op(Op::Tuple, _) => self, _ => Self::Op(Op::Tuple, vec![self.anno(annotation)]), } } pub const fn is_place(&self) -> bool { matches!( self, Self::Id(_) | Self::Op(Op::Index | Op::Dot | Op::Deref, _) ) } pub const fn is_value(&self) -> bool { !self.is_place() } #[allow(clippy::type_complexity)] pub const fn as_slice(&self) -> Option<(Op, &[Anno, A>])> { match self { Expr::Op(op, args) => Some((*op, args.as_slice())), _ => None, } } } impl Pat { pub fn to_tuple(self) -> Self { match self { Self::Op(PatOp::Tuple, _) => self, _ => Self::Op(PatOp::Tuple, vec![self]), } } } impl From<&str> for Path { fn from(value: &str) -> Self { Self { parts: vec![value.to_owned()] } } }