ast: - Document operators - Parameterize Pat with Annotation - Consolidate Path and Lit into "Value" (Expr) - Consolidate NamedRecord/Namedtuple into PatOp::TypePrefixed - Allow Pat<Pat,*> patterns - Additional helper functions on Expr and Pat lexer: - Support inner-doc comment syntax `//!` - Cleans up `///` or `//!` prefix parser: - Make Parse::Prec `Default` - Allow expression elision after `..`/`..=` - Fix Parser::consume not updating elide_do state - Add token splitting, to make `&&Expr` and `&&Pat` easier - error: Embed Pat precedence in ParseError
422 lines
10 KiB
Rust
422 lines
10 KiB
Rust
//! 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<T: Clone + std::fmt::Debug + std::fmt::Display + PartialEq + Eq> Annotation for T {}
|
|
|
|
/// A value with an annotation.
|
|
#[derive(Clone, PartialEq, Eq)]
|
|
pub struct Anno<T: Annotation, A: Annotation = Span>(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<A: Annotation = Span> {
|
|
/// 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<Bind<A>>),
|
|
/// Expr { (Ident (: Expr)?),* }
|
|
Make(Box<Make<A>>),
|
|
/// Op Expr | Expr Op | Expr (Op Expr)+ | Op Expr Expr else Expr
|
|
Op(Op, Vec<Anno<Self, A>>),
|
|
}
|
|
|
|
/// 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 { <Bind(Match, ..)>,* }`
|
|
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<String>,
|
|
// 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>),
|
|
/// { Use, * }
|
|
Tree(Vec<Use>),
|
|
}
|
|
|
|
/// 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<A: Annotation = Span>(
|
|
pub BindOp,
|
|
pub Vec<Path>,
|
|
pub Pat<A>,
|
|
pub Vec<Anno<Expr<A>, 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<A: Annotation = Span>(pub Anno<Expr<A>, A>, pub Vec<MakeArm<A>>);
|
|
|
|
/// A single "arm" of a make expression
|
|
/// ```text
|
|
/// Identifier (':' Expr)?
|
|
/// ```
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct MakeArm<A: Annotation = Span>(pub String, pub Option<Anno<Expr<A>, 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<A: Annotation = Span> {
|
|
/// 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<Anno<Expr<A>, A>>),
|
|
/// Matches a compound pattern
|
|
Op(PatOp, Vec<Pat<A>>),
|
|
}
|
|
|
|
/// 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<T: Annotation, A: Annotation> std::fmt::Debug for Anno<T, A> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
<A as std::fmt::Debug>::fmt(&self.1, f)?;
|
|
f.write_str(": ")?;
|
|
<T as std::fmt::Debug>::fmt(&self.0, f)
|
|
}
|
|
}
|
|
impl<A: Annotation> Default for Expr<A> {
|
|
fn default() -> Self {
|
|
Self::Op(Op::Tuple, vec![])
|
|
}
|
|
}
|
|
|
|
impl<A: Annotation> Expr<A> {
|
|
pub const fn anno(self, annotation: A) -> Anno<Expr<A>, A> {
|
|
Anno(self, annotation)
|
|
}
|
|
|
|
pub fn and_do(self, annotation: A, other: Anno<Expr<A>, 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<Expr<A>, A>])> {
|
|
match self {
|
|
Expr::Op(op, args) => Some((*op, args.as_slice())),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<A: Annotation> Pat<A> {
|
|
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()] }
|
|
}
|
|
}
|