Files
Doughlang/src/ast.rs

643 lines
20 KiB
Rust

//! The Abstract Syntax Tree defines an interface between the parser and type checker
pub mod macro_matcher;
/// A value with an annotation.
#[derive(Clone, PartialEq, Eq)]
pub struct Anno<T: Annotation, A: Annotation = Span>(pub T, pub A);
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)
}
}
/// 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 qualified identifier
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FqPath {
// TODO: Identifier interning
pub parts: Vec<String>,
// TODO: generic parameters
}
impl From<&str> for FqPath {
fn from(value: &str) -> Self {
Self { parts: vec![value.to_owned()] }
}
}
/// 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>),
/// { Use, * }
Tree(Vec<Use>),
}
/// 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(FqPath),
/// Matches a Struct Expression `Ident { Pat }`
NamedStruct(FqPath, Box<Pat>),
/// Matches a Tuple Struct Expression `Ident ( Pat )`
NamedTuple(FqPath, Box<Pat>),
/// Matches a literal value by equality comparison
Lit(Literal),
/// Matches a compound pattern
Op(PatOp, Vec<Pat>),
}
/// Operators on lists of patterns
#[derive(Clone, 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 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,
}
/// A pattern binding
/// ```ignore
/// let Pat (= Expr (else Expr)?)?
/// const Pat (= Expr (else Expr)?)?
/// static Pat (= Expr (else Expr)?)?
/// type Pat (= Expr)?
/// fn Pat Expr
/// mod Pat Expr
/// impl Pat Expr
/// Pat => Expr // in match
/// ``````
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Bind<A: Annotation = Span>(pub BindKind, pub Pat, pub Vec<Anno<Expr<A>, A>>);
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum BindKind {
/// 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 `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>>);
/// A record-type definition
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Typedef(pub TypedefKind, pub Pat);
/// The type of type being defined
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TypedefKind {
Struct,
Enum,
}
/// Expressions: The beating heart of Dough
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Expr<A: Annotation = Span> {
/// An identifier
Id(FqPath),
/// A meta-identifier
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<Bind<A>>),
/// (struct | enum | type) Pat::NoTopAlt
Struct(Box<Typedef>),
/// 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>>),
}
#[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 { <Let(Match, ..)>,* }
If, // if Expr Expr (else Expr)?
While, // while Expr Expr (else Expr)?
Break, // break Expr
Return, // return Expr
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
}
impl<A: Annotation> Default for Expr<A> {
fn default() -> Self {
Self::Op(Op::Tuple, vec![])
}
}
impl<A: Annotation> Expr<A> {
pub 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 is_place(&self) -> bool {
matches!(
self,
Self::Id(_) | Self::Op(Op::Index, _) | Self::Op(Op::Dot, _) | Self::Op(Op::Deref, _)
)
}
#[allow(clippy::type_complexity)]
pub fn as_slice(&self) -> Option<(Op, &[Anno<Expr<A>, A>])> {
match self {
Expr::Op(op, args) => Some((*op, args.as_slice())),
_ => None,
}
}
}
use crate::{fmt::FmtAdapter, span::Span};
use std::{fmt::Display, format_args as fmt};
impl Display for Literal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Bool(v) => v.fmt(f),
Self::Char(c) => write!(f, "'{}'", c.escape_debug()),
Self::Int(i, 2) => write!(f, "0b{i:b}"),
Self::Int(i, 8) => write!(f, "0o{i:o}"),
Self::Int(i, 16) => write!(f, "0x{i:x}"),
Self::Int(i, _) => i.fmt(f),
Self::Str(s) => write!(f, "\"{}\"", s.escape_debug()),
}
}
}
impl Display for FqPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { parts } = self;
f.list(parts, "::")
}
}
impl<T: Display + Annotation, A: Annotation> Display for Anno<T, A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl Display for Use {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Glob => "*".fmt(f),
Self::Name(name) => name.fmt(f),
Self::Path(segment, rest) => write!(f, "{segment}::{rest}"),
Self::Tree(items) => match items.len() {
0 => "{}".fmt(f),
1..=3 => f.delimit("{ ", " }").list(items, ", "),
_ => f
.delimit_indented("{", "}")
.list_wrap("\n", items, ",\n", ",\n"),
},
}
}
}
impl<A: Annotation> Display for Bind<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self(op, pat, exprs) = self;
match op {
BindKind::Match => f.delimit(fmt!("{pat} => "), "").list(exprs, ",!? "),
BindKind::Fn | BindKind::Mod | BindKind::Impl => {
f.delimit(fmt!("{op}{pat} "), "").list(exprs, ",!? ")
}
_ => match exprs.as_slice() {
[] => write!(f, "{op}{pat}"),
[value] => write!(f, "{op}{pat} = {value}"),
[value, fail] => write!(f, "{op} {pat} = {value} else {fail}"),
other => f.delimit(fmt!("{op}{pat} ("), ")").list(other, ", "),
},
}
}
}
impl Display for BindKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Let => "let ",
Self::Const => "const ",
Self::Static => "static ",
Self::Type => "type ",
Self::Fn => "fn ",
Self::Mod => "mod ",
Self::Impl => "impl ",
Self::Match => "| ",
})
}
}
impl<A: Annotation> Display for Make<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self(expr, make_arms) = self;
f.delimit(fmt!("({expr} {{"), "})").list(make_arms, ", ")
}
}
impl Display for Typedef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self(kind, pat) = self;
f.write_str(match kind {
TypedefKind::Struct => "struct ",
TypedefKind::Enum => "enum ",
})?;
match pat {
Pat::NamedStruct(name, bind) => match bind.as_ref() {
Pat::Op(PatOp::Tuple, parts) => f
.delimit_indented(fmt!("{name} {{"), "}")
.list_wrap("\n", parts, ",\n", ",\n"),
other => write!(f, "{name} {{ {other} }}"),
},
_ => pat.fmt(f),
}
}
}
impl<A: Annotation> Display for Expr<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Id(id) => id.fmt(f),
Self::MetId(id) => write!(f, "`{id}"),
Self::Lit(literal) => literal.fmt(f),
Self::Use(v) => write!(f, "use {v}"),
Self::Bind(v) => v.fmt(f),
Self::Struct(v) => v.fmt(f),
Self::Make(v) => v.fmt(f),
Self::Op(op @ (Op::If | Op::While), exprs) => match exprs.as_slice() {
[cond, pass, Anno(Expr::Op(Op::Tuple, e), _)] if e.is_empty() => {
write!(f, "{op}{cond} {pass}")
}
[cond, pass, fail] => write!(f, "{op}{cond} {pass} else {fail}"),
other => f.delimit(fmt!("({op}, "), ")").list(other, ", "),
},
Self::Op(op @ Op::Match, exprs) => match exprs.as_slice() {
[scrutinee, arms @ ..] => f
.delimit_indented(fmt!("{op}{scrutinee} {{"), "}")
.list_wrap("\n", arms, ",\n", ",\n"),
[] => write!(f, "{op} () {{}}"), // invalid, but whatever.
},
Self::Op(Op::Array, exprs) => f.delimit("[", "]").list(exprs, ", "),
Self::Op(Op::ArRep, exprs) => f.delimit("[", "]").list(exprs, "; "),
Self::Op(Op::Block, exprs) => f
.delimit_indented("{", "}")
.list_wrap("\n", exprs, "\n", "\n"),
Self::Op(Op::Tuple, exprs) => f.delimit("(", ")").list(exprs, ", "),
Self::Op(Op::Group, exprs) => f.list(exprs, ", "),
Self::Op(Op::Meta, exprs) => match exprs.as_slice() {
[meta, expr @ ..] => f.delimit(fmt!("#[{meta}] "), "").list(expr, ","),
[] => write!(f, "#[]"),
},
Self::Op(op @ Op::Call, exprs) => match exprs.as_slice() {
[callee, args @ ..] => f.delimit(fmt!("{callee}("), ")").list(args, ", "),
[] => write!(f, "{op}"),
},
Self::Op(op @ Op::Index, exprs) => match exprs.as_slice() {
[callee, args @ ..] => f.delimit(fmt!("{callee}["), "]").list(args, ", "),
[] => write!(f, "{op}"),
},
Self::Op(op @ Op::Do, exprs) => f.list(exprs, op),
Self::Op(op @ Op::Macro, exprs) => f.delimit(op, "").list(exprs, " => "),
Self::Op(op @ Op::Try, exprs) => f.delimit("(", fmt!("){op}")).list(exprs, ", "),
Self::Op(op, exprs) => match exprs.as_slice() {
[one] => write!(f, "{op}{one}"),
many => f.delimit("(", ")").list(many, op),
},
}
}
}
impl Display for Op {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Op::Do => ";\n",
Op::As => " as ",
Op::Macro => "macro ",
Op::Block => "{}",
Op::Array => "[]",
Op::ArRep => "[;]",
Op::Group => "()",
Op::Tuple => "()",
Op::Meta => "#[]",
Op::Try => "?",
Op::Index => "",
Op::Call => "",
Op::Pub => "pub ",
Op::Loop => "loop ",
Op::Match => "match ",
Op::If => "if ",
Op::While => "while ",
Op::Break => "break ",
Op::Return => "return ",
Op::Dot => ".",
Op::RangeEx => "..",
Op::RangeIn => "..=",
Op::Neg => "-",
Op::Not => "!",
Op::Identity => "!!",
Op::Refer => "&",
Op::Deref => "*",
Op::Mul => " * ",
Op::Div => " / ",
Op::Rem => " % ",
Op::Add => " + ",
Op::Sub => " - ",
Op::Shl => " << ",
Op::Shr => " >> ",
Op::And => " & ",
Op::Xor => " ^ ",
Op::Or => " | ",
Op::Lt => " < ",
Op::Leq => " <= ",
Op::Eq => " == ",
Op::Neq => " != ",
Op::Geq => " >= ",
Op::Gt => " > ",
Op::LogAnd => " && ",
Op::LogXor => " ^^ ",
Op::LogOr => " || ",
Op::Set => " = ",
Op::MulSet => " *= ",
Op::DivSet => " /= ",
Op::RemSet => " %= ",
Op::AddSet => " += ",
Op::SubSet => " -= ",
Op::ShlSet => " <<= ",
Op::ShrSet => " >>= ",
Op::AndSet => " &= ",
Op::XorSet => " ^= ",
Op::OrSet => " |= ",
})
}
}
impl<A: Annotation> Display for MakeArm<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self(name, Some(body)) => write!(f, "{name}: {body}"),
Self(name, None) => write!(f, "{name}"),
}
}
}
impl Display for Pat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Ignore => "_".fmt(f),
Self::Never => "!".fmt(f),
Self::Lit(literal) => literal.fmt(f),
Self::MetId(name) => write!(f, "`{name}"),
Self::Name(name) => name.fmt(f),
Self::Path(path) => path.fmt(f),
Self::NamedStruct(name, bind) => match bind.as_ref() {
Pat::Op(PatOp::Tuple, parts) => {
f.delimit(fmt!("{name} {{ "), " }").list(parts, ", ")
}
other => write!(f, "{name} {{ {other} }}"),
},
Self::NamedTuple(name, bind) => write!(f, "{name} {bind}"),
Self::Op(PatOp::Tuple, pats) => f.delimit("(", ")").list(pats, ", "),
Self::Op(PatOp::Slice, pats) => f.delimit("[", "]").list(pats, ", "),
Self::Op(op @ PatOp::Arrep, pats) => f.delimit("[", "]").list(pats, op),
Self::Op(op @ (PatOp::Typed | PatOp::Fn), pats) => f.list(pats, op),
Self::Op(op @ PatOp::Alt, pats) => f.delimit("<", ">").list(pats, op),
Self::Op(op, pats) => match pats.as_slice() {
[] => op.fmt(f),
[rest] => write!(f, "{op}{rest}"),
_ => f.delimit("(", ")").list(pats, op),
},
}
}
}
impl Display for PatOp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Pub => "pub ",
Self::Mut => "mut ",
Self::Ref => "&",
Self::Rest => "..",
Self::RangeEx => "..",
Self::RangeIn => "..=",
Self::Tuple => ", ",
Self::Slice => ", ",
Self::Arrep => "; ",
Self::Typed => ": ",
Self::Fn => " -> ",
Self::Alt => " | ",
})
}
}
impl<A: Annotation> TryFrom<Expr<A>> for Pat {
type Error = Expr<A>;
fn try_from(value: Expr<A>) -> Result<Self, Self::Error> {
Ok(match value {
Expr::Id(FqPath { mut parts }) if parts.len() == 1 => {
match parts.pop().expect("parts should have len 1") {
ig if ig == "_" => Self::Ignore,
name => Self::Name(name),
}
}
Expr::Id(path) => Self::Path(path),
Expr::MetId(name) => Self::MetId(name),
Expr::Lit(literal) => Self::Lit(literal),
Expr::Op(Op::RangeEx, exprs) => Self::Op(
if exprs.len() > 1 {
PatOp::RangeEx
} else {
PatOp::Rest
},
exprs
.into_iter()
.map(Self::try_from)
.collect::<Result<Vec<_>, Expr<A>>>()?,
),
Expr::Op(Op::Tuple, exprs) => Self::Op(
PatOp::Tuple,
exprs
.into_iter()
.map(Self::try_from)
.collect::<Result<_, _>>()?,
),
Expr::Op(Op::Array, exprs) => Self::Op(
PatOp::Slice,
exprs
.into_iter()
.map(Self::try_from)
.collect::<Result<_, _>>()?,
),
other => Err(other)?,
})
}
}
impl<A: Annotation> TryFrom<Anno<Expr<A>, A>> for Pat {
type Error = Expr<A>;
fn try_from(value: Anno<Expr<A>, A>) -> Result<Self, Self::Error> {
Self::try_from(value.0)
}
}