//! 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 | const | static) 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 { // -- true operators Do, // Expr ; Expr As, // Expr as Expr Macro, // macro { (Pat => Expr)* } Block, // { Expr } Array, // [ Expr,* ] ArRep, // [ Expr ; Expr ] Group, // ( Expr ,?) Tuple, // Expr (, Expr)* Meta, // #[ Expr ] Try, // Expr '?' Index, // Expr [ Expr,* ] Call, // Expr ( Expr,* ) Pub, // pub Expr Loop, // loop Expr Match, // match Expr { ,* } If, // if Expr Expr (else Expr)? While, // while Expr Expr (else Expr)? Break, // break Expr Return, // return Expr Continue, // continue Dot, // Expr . Expr RangeEx, // Expr? ..Expr RangeIn, // Expr? ..=Expr Neg, // -Expr Not, // !Expr Identity, // !!Expr Refer, // &Expr Deref, // *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, // Expr |= Expr } /// 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 :: Use Path(String, Box), /// { Use, * } Tree(Vec), } /// A pattern binding /// ```ignore /// let Pat (= Expr (else Expr)?)? /// const Pat (= Expr (else Expr)?)? /// static Pat (= Expr (else Expr)?)? /// type Pat (= Expr)? /// struct Pat /// enum Pat /// fn Pat Expr /// mod Pat Expr /// impl Pat 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 `const Pat = Expr` binding Const, /// A `static Pat = Expr` binding Static, /// 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 `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 against a named const value Path(Path), /// Matches a Struct Expression `Ident { Pat }` NamedStruct(Path, Box), /// Matches a Tuple Struct Expression `Ident ( Pat )` NamedTuple(Path, Box), /// Matches a literal value by equality comparison Lit(Literal), /// 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 tuple Tuple, /// Matches the elements of a slice or array Slice, /// Matches a constant-size slice with repeating elements Arrep, /// Matches a type annotation or struct member Typed, /// Matches a function signature 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 const fn is_place(&self) -> bool { matches!( self, Self::Id(_) | Self::Op(Op::Index | Op::Dot | Op::Deref, _) ) } #[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 From<&str> for Path { fn from(value: &str) -> Self { Self { parts: vec![value.to_owned()] } } }