doughlang: Expressions in patterns

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
This commit is contained in:
2026-01-05 15:17:22 -05:00
parent d6104b6a0b
commit e4c008bd4b
16 changed files with 700 additions and 322 deletions

View File

@@ -130,7 +130,7 @@ impl<'a> Visit<'a> for CountNodes {
item.children(self) item.children(self)
} }
fn visit_pat(&mut self, item: &'a Pat) -> Result<(), Self::Error> { fn visit_pat<A: Annotation>(&mut self, item: &'a Pat<A>) -> Result<(), Self::Error> {
self.patterns += 1; self.patterns += 1;
item.children(self) item.children(self)
} }

View File

@@ -54,6 +54,8 @@ impl ToLisp {
PatOp::Slice => "slice", PatOp::Slice => "slice",
PatOp::ArRep => "ar-rep", PatOp::ArRep => "ar-rep",
PatOp::Typed => "typed", PatOp::Typed => "typed",
PatOp::TypePrefixed => "type-prefixed",
PatOp::Generic => "generic-in",
PatOp::Fn => "fn", PatOp::Fn => "fn",
PatOp::Alt => "alt", PatOp::Alt => "alt",
} }
@@ -196,28 +198,13 @@ impl<'a> Visit<'a> for ToLisp {
Ok(()) Ok(())
} }
fn visit_pat(&mut self, item: &'a Pat) -> Result<(), Self::Error> { fn visit_pat<A: Annotation>(&mut self, item: &'a Pat<A>) -> Result<(), Self::Error> {
match item { match item {
Pat::Ignore => print!("(ignore)"), Pat::Ignore => print!("(ignore)"),
Pat::Never => print!("(never)"), Pat::Never => print!("(never)"),
Pat::MetId(id) => print!("(replace {id})"), Pat::MetId(id) => print!("(replace {id})"),
Pat::Name(name) => print!("{name}"), Pat::Name(name) => print!("{name}"),
Pat::Path(path) => path.visit_in(self)?, Pat::Value(literal) => literal.visit_in(self)?,
Pat::NamedRecord(path, pat) => {
print!("(struct-pat ");
path.visit_in(self)?;
print!(" ");
pat.visit_in(self)?;
print!(")")
}
Pat::NamedTuple(path, pat) => {
print!("(named ");
path.visit_in(self)?;
print!(" ");
pat.visit_in(self)?;
print!(")")
}
Pat::Lit(literal) => literal.visit_in(self)?,
Pat::Op(pat_op, pats) => { Pat::Op(pat_op, pats) => {
print!("({}", self.pat_op(*pat_op)); print!("({}", self.pat_op(*pat_op));
for pat in pats { for pat in pats {

140
examples/weight.rs Normal file
View File

@@ -0,0 +1,140 @@
//! Demonstrates the visitor pattern
use std::{convert::Infallible, error::Error};
use doughlang::{
ast::{
visit::{Visit, Walk},
*,
},
lexer::Lexer,
parser::{Parser, expr::Prec},
};
const CODE: &str = r#"
let x: [i32; 4] = [1, 2, 3, 4];
use foo::bar::baz::qux::{a, b, c, d, e};
struct Point<T> {x: T, y: T}
let v = Point {
x, y: 20 + x
}
fn main() -> (&str, bool, i128) {
// An if expression is like the ternary conditional operator in C
let y = if 10 < 50 {
"\u{1f988}"
} else {
"x"
}
// A `while` expression is like the while-else construct in Python,
// but it returns a value via the `break` keyword
let z = while false {
// do a thing repeatedly
break true
} else {
// If `while` does not `break`, fall through to the `else` expression
false
}
// The same is true of `for` expressions!
let w = for idx in 0..100 {
if idx > 2 * 2 {
break idx
}
} else 12345; // semicolon operator required here
// desugars to
let w = match ((0..100).into_iter()) {
__iter => loop match (__iter.next()) {
None => break 12345,
Some (idx) => {
if (idx > (2 * 2)) {
break idx
}
},
},
};
// A block evaluates to its last expression,
// or Empty if there is none
// (🦈, false, 5)
(y, z, w)
}
"#;
fn main() -> Result<(), Box<dyn Error>> {
let ast: Anno<Expr> = Parser::new(Lexer::new(CODE)).parse(Prec::MIN)?;
println!("{ast}");
let mut counters = Weight::new();
counters.visit(&ast);
println!("{counters:#?}");
println!("{}", CODE.len());
Ok(())
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
struct Weight {
size: usize,
}
impl Weight {
fn new() -> Self {
Self::default()
}
}
impl<'a> Visit<'a> for Weight {
type Error = Infallible;
fn visit_expr<A: Annotation>(&mut self, item: &'a Expr<A>) -> Result<(), Self::Error> {
self.size += size_of_val(item);
item.children(self)
}
fn visit_ident(&mut self, item: &'a str) -> Result<(), Self::Error> {
self.size += size_of_val(item);
item.children(self)
}
fn visit_path(&mut self, item: &'a Path) -> Result<(), Self::Error> {
self.size += size_of_val(item) + size_of_val(item.parts.as_slice());
item.children(self)
}
fn visit_literal(&mut self, item: &'a Literal) -> Result<(), Self::Error> {
self.size += size_of_val(item);
item.children(self)
}
fn visit_use(&mut self, item: &'a Use) -> Result<(), Self::Error> {
self.size += size_of_val(item);
item.children(self)
}
fn visit_pat<A: Annotation>(&mut self, item: &'a Pat<A>) -> Result<(), Self::Error> {
self.size += size_of_val(item);
item.children(self)
}
fn visit_bind<A: Annotation>(&mut self, item: &'a Bind<A>) -> Result<(), Self::Error> {
self.size += size_of_val(item);
item.children(self)
}
fn visit_make<A: Annotation>(&mut self, item: &'a Make<A>) -> Result<(), Self::Error> {
self.size += size_of_val(item);
item.children(self)
}
fn visit_makearm<A: Annotation>(&mut self, item: &'a MakeArm<A>) -> Result<(), Self::Error> {
self.size += size_of_val(item);
item.children(self)
}
}

12
samples/elf.cl Normal file
View File

@@ -0,0 +1,12 @@
use num::{
i32, u32, u16, u8
}
type Elf32_Addr, Elf32_Half, Elf32_Off, Elf32_Sword, Elf32_Word, UChar = u32, u16, u32, i32, u32, u8;
let <T> Option<T> = type Some(T) | None;
// is equivalent to
enum Option<T> {
Some(T),
None
};

View File

@@ -7,15 +7,15 @@ enum Expr {
fn execute(expr: Expr) -> f64 { fn execute(expr: Expr) -> f64 {
match expr { match expr {
ExprAtom(value) => value, Expr::Atom(value) => value,
ExprOp('*', [lhs, rhs]) => execute(lhs) * execute(rhs), Expr::Op('*', [lhs, rhs]) => execute(lhs) * execute(rhs),
ExprOp('/', [lhs, rhs]) => execute(lhs) / execute(rhs), Expr::Op('/', [lhs, rhs]) => execute(lhs) / execute(rhs),
ExprOp('%', [lhs, rhs]) => execute(lhs) % execute(rhs), Expr::Op('%', [lhs, rhs]) => execute(lhs) % execute(rhs),
ExprOp('+', [lhs, rhs]) => execute(lhs) + execute(rhs), Expr::Op('+', [lhs, rhs]) => execute(lhs) + execute(rhs),
ExprOp('-', [lhs, rhs]) => execute(lhs) - execute(rhs), Expr::Op('-', [lhs, rhs]) => execute(lhs) - execute(rhs),
// ExprOp('>', [lhs, rhs]) => (execute(lhs) as u64 >> execute(rhs) as u64) as f64, // Expr::Op('>', [lhs, rhs]) => (execute(lhs) as u64 >> execute(rhs) as u64) as f64,
// ExprOp('<', [lhs, rhs]) => (execute(lhs) as u64 << execute(rhs) as u64) as f64, // Expr::Op('<', [lhs, rhs]) => (execute(lhs) as u64 << execute(rhs) as u64) as f64,
ExprOp('-', [lhs]) => - execute(lhs), Expr::Op('-', [lhs]) => - execute(lhs),
other => { other => {
panic("Unknown operation: " + fmt(other)) panic("Unknown operation: " + fmt(other))
} }
@@ -25,9 +25,9 @@ fn execute(expr: Expr) -> f64 {
/// Formats an expression to a string /// Formats an expression to a string
fn fmt_expr(expr: Expr) -> str { fn fmt_expr(expr: Expr) -> str {
match expr { match expr {
ExprAtom(value) => fmt(value), Expr::Atom(value) => fmt(value),
ExprOp(operator, [lhs, rhs]) => fmt('(', fmt_expr(lhs), ' ', operator, ' ', fmt_expr(rhs), ')'), Expr::Op(operator, [lhs, rhs]) => fmt('(', fmt_expr(lhs), ' ', operator, ' ', fmt_expr(rhs), ')'),
ExprOp(operator, [rhs]) => fmt(operator, fmt_expr(rhs)), Expr::Op(operator, [rhs]) => fmt(operator, fmt_expr(rhs)),
_ => println("Unexpected expr: ", expr), _ => println("Unexpected expr: ", expr),
} }
} }

View File

@@ -65,78 +65,137 @@ pub enum Expr<A: Annotation = Span> {
/// - Traditional binary and unary operators (add, sub, neg, assign) /// - Traditional binary and unary operators (add, sub, neg, assign)
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Op { pub enum Op {
// -- true operators /// `Expr (; Expr)*`
Do, // Expr ; Expr Do,
As, // Expr as Expr /// `Expr as Expr`
Macro, // macro { (Pat => Expr)* } As,
Block, // { Expr } /// `macro { (Pat => Expr)* }`
Array, // [ Expr,* ] Macro,
ArRep, // [ Expr ; Expr ] /// `{ Expr }`
Group, // ( Expr ,?) Block,
Tuple, // Expr (, Expr)* /// `[ Expr,* ]`
Meta, // #[ Expr ] Array,
/// `[ Expr ; Expr ]`
ArRep,
/// `( Expr )`
Group,
/// `Expr (, Expr)*`
Tuple,
/// `#[ Expr ]`
Meta,
Try, // Expr '?' /// `Expr '?'`
Index, // Expr [ Expr,* ] Try,
Call, // Expr ( Expr,* ) /// `Expr [ Expr,* ]`
Index,
/// `Expr ( Expr,* )`
Call,
Pub, // pub Expr /// `pub Expr`
Const, // const Expr Pub,
Static, // static Expr /// `const Expr`
Loop, // loop Expr Const,
Match, // match Expr { <Bind(Match, ..)>,* } /// `static Expr`
If, // if Expr Expr (else Expr)? Static,
While, // while Expr Expr (else Expr)? /// `loop Expr`
Break, // break Expr Loop,
Return, // return Expr /// `match Expr { <Bind(Match, ..)>,* }`
Continue, // continue Match,
/// `if Expr Expr (else Expr)?`
If,
/// `while Expr Expr (else Expr)?`
While,
/// `break Expr`
Break,
/// `return Expr`
Return,
/// `continue`
Continue,
Dot, // Expr . Expr /// `Expr . Expr`
Dot,
RangeEx, // Expr? ..Expr /// `Expr? ..Expr`
RangeIn, // Expr? ..=Expr RangeEx,
Neg, // -Expr /// `Expr? ..=Expr`
Not, // !Expr RangeIn,
Identity, // !!Expr /// `-Expr`
Refer, // &Expr Neg,
Deref, // *Expr /// `!Expr`
Not,
/// `!!Expr`
Identity,
/// `&Expr`
Refer,
/// `*Expr`
Deref,
Mul, // Expr * Expr /// `Expr * Expr`
Div, // Expr / Expr Mul,
Rem, // Expr % Expr /// `Expr / Expr`
Div,
/// `Expr % Expr`
Rem,
Add, // Expr + Expr /// `Expr + Expr`
Sub, // Expr - Expr Add,
/// `Expr - Expr`
Sub,
Shl, // Expr << Expr /// `Expr << Expr`
Shr, // Expr >> Expr Shl,
/// `Expr >> Expr`
Shr,
And, // Expr & Expr /// `Expr & Expr`
Xor, // Expr ^ Expr And,
Or, // Expr | Expr /// `Expr ^ Expr`
Xor,
/// `Expr | Expr`
Or,
Lt, // Expr < Expr /// `Expr < Expr`
Leq, // Expr <= Expr Lt,
Eq, // Expr == Expr /// `Expr <= Expr`
Neq, // Expr != Expr Leq,
Geq, // Expr >= Expr /// `Expr == Expr`
Gt, // Expr > Expr Eq,
/// `Expr != Expr`
Neq,
/// `Expr >= Expr`
Geq,
/// `Expr > Expr`
Gt,
LogAnd, // Expr && Expr /// `Expr && Expr`
LogXor, // Expr ^^ Expr LogAnd,
LogOr, // Expr || Expr /// `Expr ^^ Expr`
LogXor,
/// `Expr || Expr`
LogOr,
Set, // Expr = Expr /// `Expr = Expr`
MulSet, // Expr *= Expr Set,
DivSet, // Expr /= Expr /// `Expr *= Expr`
RemSet, // Expr %= Expr MulSet,
AddSet, // Expr += Expr /// `Expr /= Expr`
SubSet, // Expr -= Expr DivSet,
ShlSet, // Expr <<= Expr /// `Expr %= Expr`
ShrSet, // Expr >>= Expr RemSet,
AndSet, // Expr &= Expr /// `Expr += Expr`
XorSet, // Expr ^= Expr AddSet,
OrSet, // Expr |= Expr /// `Expr -= Expr`
SubSet,
/// `Expr <<= Expr`
ShlSet,
/// `Expr >>= Expr`
ShrSet,
/// `Expr &= Expr`
AndSet,
/// `Expr ^= Expr`
XorSet,
/// `Expr |= Expr`
OrSet,
} }
/// A qualified identifier /// A qualified identifier
@@ -181,11 +240,11 @@ pub enum Use {
/// ```ignore /// ```ignore
/// let Pat (= Expr (else Expr)?)? /// let Pat (= Expr (else Expr)?)?
/// type Pat (= Expr)? /// type Pat (= Expr)?
/// struct Pat
/// enum Pat
/// fn Pat Expr /// fn Pat Expr
/// mod Pat Expr /// mod Pat Expr
/// impl Pat Expr /// impl Pat Expr
/// struct Pat
/// enum Pat
/// for Pat in Expr Expr (else Expr)? /// for Pat in Expr Expr (else Expr)?
/// Pat => Expr // in match /// Pat => Expr // in match
/// ``` /// ```
@@ -193,7 +252,7 @@ pub enum Use {
pub struct Bind<A: Annotation = Span>( pub struct Bind<A: Annotation = Span>(
pub BindOp, pub BindOp,
pub Vec<Path>, pub Vec<Path>,
pub Pat, pub Pat<A>,
pub Vec<Anno<Expr<A>, A>>, pub Vec<Anno<Expr<A>, A>>,
); );
@@ -237,7 +296,7 @@ pub struct MakeArm<A: Annotation = Span>(pub String, pub Option<Anno<Expr<A>, A>
/// ///
/// This covers both patterns in Match expressions, and type annotations. /// This covers both patterns in Match expressions, and type annotations.
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum Pat { pub enum Pat<A: Annotation = Span> {
/// Matches anything without binding /// Matches anything without binding
Ignore, Ignore,
/// Matches nothing, ever /// Matches nothing, ever
@@ -246,16 +305,10 @@ pub enum Pat {
MetId(String), MetId(String),
/// Matches anything, and binds it to a name /// Matches anything, and binds it to a name
Name(String), Name(String),
/// Matches against a named const value /// Matches a value by equality comparison
Path(Path), Value(Box<Anno<Expr<A>, A>>),
/// Matches a Struct Expression `Ident { Pat }`
NamedRecord(Path, Box<Pat>),
/// Matches a Tuple Struct Expression `Ident ( Pat )`
NamedTuple(Path, Box<Pat>),
/// Matches a literal value by equality comparison
Lit(Literal),
/// Matches a compound pattern /// Matches a compound pattern
Op(PatOp, Vec<Pat>), Op(PatOp, Vec<Pat<A>>),
} }
/// Operators on lists of patterns /// Operators on lists of patterns
@@ -285,6 +338,10 @@ pub enum PatOp {
ArRep, ArRep,
/// Matches a type annotation or struct member /// Matches a type annotation or struct member
Typed, Typed,
/// Matches a prefix-type-annotated structure
TypePrefixed,
/// Matches a generic specialization annotation
Generic,
/// Changes the binding mode to "function-body" /// Changes the binding mode to "function-body"
Fn, Fn,
/// Matches one of a list of alternatives /// Matches one of a list of alternatives
@@ -321,6 +378,13 @@ impl<A: Annotation> Expr<A> {
Self::Op(Op::Do, exprs) 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 { pub const fn is_place(&self) -> bool {
matches!( matches!(
self, self,
@@ -328,6 +392,10 @@ impl<A: Annotation> Expr<A> {
) )
} }
pub const fn is_value(&self) -> bool {
!self.is_place()
}
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub const fn as_slice(&self) -> Option<(Op, &[Anno<Expr<A>, A>])> { pub const fn as_slice(&self) -> Option<(Op, &[Anno<Expr<A>, A>])> {
match self { match self {
@@ -337,6 +405,15 @@ impl<A: Annotation> Expr<A> {
} }
} }
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 { impl From<&str> for Path {
fn from(value: &str) -> Self { fn from(value: &str) -> Self {
Self { parts: vec![value.to_owned()] } Self { parts: vec![value.to_owned()] }

View File

@@ -46,7 +46,10 @@ impl<A: Annotation> Display for Expr<A> {
}, },
Self::Op(op @ Op::Call, exprs) => match exprs.as_slice() { Self::Op(op @ Op::Call, exprs) => match exprs.as_slice() {
[callee, args @ ..] => f.delimit(fmt!("{callee}("), ")").list(args, ", "), [callee, Anno(Expr::Op(Op::Tuple, args), _)] => {
f.delimit(fmt!("{callee}("), ")").list(args, ", ")
}
[callee, args @ ..] => f.delimit(fmt!("{callee}(?"), "?)").list(args, ", "),
[] => write!(f, "{op}"), [] => write!(f, "{op}"),
}, },
Self::Op(op @ Op::Index, exprs) => match exprs.as_slice() { Self::Op(op @ Op::Index, exprs) => match exprs.as_slice() {
@@ -191,11 +194,14 @@ impl<A: Annotation> Display for Bind<A> {
} }
} }
BindOp::Struct | BindOp::Enum => match pat { BindOp::Struct | BindOp::Enum => match pat {
Pat::NamedRecord(name, bind) => match bind.as_ref() { Pat::Op(PatOp::TypePrefixed, bind) => match bind.as_slice() {
Pat::Op(PatOp::Tuple, parts) => f [name, Pat::Op(PatOp::Record, parts)] => f
.delimit_indented(fmt!("{name} {{"), "}") .delimit_indented(fmt!("{name} {{"), "}")
.list_wrap("\n", parts, ",\n", ",\n"), .list_wrap("\n", parts, ",\n", ",\n"),
other => write!(f, "{name} {{ {other} }}"), [name, Pat::Op(PatOp::Tuple, parts)] => {
f.delimit(fmt!("{name}("), ")").list(parts, ", ")
}
_ => pat.fmt(f),
}, },
_ => pat.fmt(f), _ => pat.fmt(f),
}, },
@@ -248,22 +254,14 @@ impl<A: Annotation> Display for MakeArm<A> {
} }
} }
impl Display for Pat { impl<A: Annotation> Display for Pat<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::Ignore => "_".fmt(f), Self::Ignore => "_".fmt(f),
Self::Never => "!".fmt(f), Self::Never => "!".fmt(f),
Self::Lit(literal) => literal.fmt(f), Self::Value(literal) => literal.fmt(f),
Self::MetId(name) => write!(f, "`{name}"), Self::MetId(name) => write!(f, "`{name}"),
Self::Name(name) => name.fmt(f), Self::Name(name) => name.fmt(f),
Self::Path(path) => path.fmt(f),
Self::NamedRecord(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::Record, pats) => f.delimit("{ ", " }").list(pats, ", "), Self::Op(PatOp::Record, pats) => f.delimit("{ ", " }").list(pats, ", "),
Self::Op(PatOp::Tuple, pats) => f.delimit("(", ")").list(pats, ", "), Self::Op(PatOp::Tuple, pats) => f.delimit("(", ")").list(pats, ", "),
Self::Op(PatOp::Slice, pats) => f.delimit("[", "]").list(pats, ", "), Self::Op(PatOp::Slice, pats) => f.delimit("[", "]").list(pats, ", "),
@@ -273,6 +271,14 @@ impl Display for Pat {
pats => f.list(pats, op), pats => f.list(pats, op),
}, },
Self::Op(op @ PatOp::Alt, pats) => f.list(pats, op), Self::Op(op @ PatOp::Alt, pats) => f.list(pats, op),
Self::Op(op @ PatOp::Generic, pats) => match pats.as_slice() {
[] => op.fmt(f),
[first, rest @ ..] => f.delimit(fmt!("{first}<"), ">").list(rest, ", "),
},
Self::Op(op @ PatOp::TypePrefixed, pats) => match pats.as_slice() {
[] => op.fmt(f),
[first, rest @ ..] => f.delimit(fmt!("{first} "), "").list(rest, ",? "),
},
Self::Op(op, pats) => match pats.as_slice() { Self::Op(op, pats) => match pats.as_slice() {
[] => op.fmt(f), [] => op.fmt(f),
[rest] => write!(f, "{op}{rest}"), [rest] => write!(f, "{op}{rest}"),
@@ -297,6 +303,8 @@ impl Display for PatOp {
Self::Slice => ", ", Self::Slice => ", ",
Self::ArRep => "[;]", Self::ArRep => "[;]",
Self::Typed => ": ", Self::Typed => ": ",
Self::Generic => "T<>",
Self::TypePrefixed => "T()",
Self::Fn => " -> ", Self::Fn => " -> ",
Self::Alt => " | ", Self::Alt => " | ",
}) })

View File

@@ -45,7 +45,7 @@ pub trait Fold<A: Annotation> {
} }
/// Consumes a [`Pat`], possibly transforms it, and produces a replacement [`Pat`] /// Consumes a [`Pat`], possibly transforms it, and produces a replacement [`Pat`]
fn fold_pat(&mut self, pat: Pat) -> Result<Pat, Self::Error> { fn fold_pat(&mut self, pat: Pat<A>) -> Result<Pat<A>, Self::Error> {
pat.children(self) pat.children(self)
} }
@@ -138,7 +138,7 @@ impl<A: Annotation> Foldable<A> for Use {
} }
} }
impl<A: Annotation> Foldable<A> for Pat { impl<A: Annotation> Foldable<A> for Pat<A> {
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> { fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
folder.fold_pat(self) folder.fold_pat(self)
} }
@@ -149,14 +149,7 @@ impl<A: Annotation> Foldable<A> for Pat {
Self::Never => Self::Never, Self::Never => Self::Never,
Self::MetId(name) => Self::MetId(name.fold_in(folder)?), Self::MetId(name) => Self::MetId(name.fold_in(folder)?),
Self::Name(name) => Self::Name(name.fold_in(folder)?), Self::Name(name) => Self::Name(name.fold_in(folder)?),
Self::Path(path) => Self::Path(path.fold_in(folder)?), Self::Value(expr) => Self::Value(expr.fold_in(folder)?),
Self::NamedRecord(path, pat) => {
Self::NamedRecord(path.fold_in(folder)?, pat.fold_in(folder)?)
}
Self::NamedTuple(path, pat) => {
Self::NamedTuple(path.fold_in(folder)?, pat.fold_in(folder)?)
}
Self::Lit(literal) => Self::Lit(literal.fold_in(folder)?),
Self::Op(op, pats) => Self::Op(op, pats.fold_in(folder)?), Self::Op(op, pats) => Self::Op(op, pats.fold_in(folder)?),
}) })
} }

View File

@@ -7,7 +7,7 @@ use std::collections::HashMap;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Subst<A: Annotation> { pub struct Subst<A: Annotation> {
pub exp: HashMap<String, Expr<A>>, pub exp: HashMap<String, Expr<A>>,
pub pat: HashMap<String, Pat>, pub pat: HashMap<String, Pat<A>>,
} }
impl<A: Annotation> Default for Subst<A> { impl<A: Annotation> Default for Subst<A> {
@@ -16,7 +16,7 @@ impl<A: Annotation> Default for Subst<A> {
} }
} }
impl<A: Annotation> Subst<A> { impl<A: Annotation> Subst<A> {
fn add_pat(&mut self, name: String, pat: &Pat) -> bool { fn add_pat(&mut self, name: String, pat: &Pat<A>) -> bool {
if self.exp.contains_key(&name) { if self.exp.contains_key(&name) {
return false; return false;
} }
@@ -161,7 +161,7 @@ impl<A: Annotation> Match<A> for MakeArm<A> {
} }
} }
impl<A: Annotation> Match<A> for Pat { impl<A: Annotation> Match<A> for Pat<A> {
fn recurse(sub: &mut Subst<A>, pat: &Self, expr: &Self) -> bool { fn recurse(sub: &mut Subst<A>, pat: &Self, expr: &Self) -> bool {
match (pat, expr) { match (pat, expr) {
(Pat::MetId(name), _) if name == "_" => true, (Pat::MetId(name), _) if name == "_" => true,
@@ -172,14 +172,8 @@ impl<A: Annotation> Match<A> for Pat {
(Pat::Never, _) => false, (Pat::Never, _) => false,
(Pat::Name(pat), Pat::Name(expr)) => pat == expr, (Pat::Name(pat), Pat::Name(expr)) => pat == expr,
(Pat::Name(_), _) => false, (Pat::Name(_), _) => false,
(Pat::Path(_), Pat::Path(_)) => true, (Pat::Value(pat), Pat::Value(expr)) => pat == expr,
(Pat::Path(_), _) => false, (Pat::Value(_), _) => false,
(Pat::NamedRecord(_, pat), Pat::NamedRecord(_, expr)) => Match::recurse(sub, pat, expr),
(Pat::NamedRecord(..), _) => false,
(Pat::NamedTuple(_, pat), Pat::NamedTuple(_, expr)) => Match::recurse(sub, pat, expr),
(Pat::NamedTuple(..), _) => false,
(Pat::Lit(pat), Pat::Lit(expr)) => pat == expr,
(Pat::Lit(_), _) => false,
(Pat::Op(_, pat), Pat::Op(_, expr)) => Match::recurse(sub, pat, expr), (Pat::Op(_, pat), Pat::Op(_, expr)) => Match::recurse(sub, pat, expr),
(Pat::Op(..), _) => false, (Pat::Op(..), _) => false,
} }
@@ -187,14 +181,13 @@ impl<A: Annotation> Match<A> for Pat {
fn apply(&mut self, sub: &Subst<A>) { fn apply(&mut self, sub: &Subst<A>) {
match self { match self {
Pat::Ignore | Pat::Never | Pat::Name(_) | Pat::Path(_) | Pat::Lit(_) => {} Pat::Ignore | Pat::Never | Pat::Name(_) => {}
Pat::Value(expr) => expr.apply(sub),
Pat::MetId(id) => { Pat::MetId(id) => {
if let Some(expr) = sub.pat.get(id) { if let Some(expr) = sub.pat.get(id) {
*self = expr.clone(); *self = expr.clone();
} }
} }
Pat::NamedRecord(_, expr) => expr.apply(sub),
Pat::NamedTuple(_, expr) => expr.apply(sub),
Pat::Op(_, pats) => pats.apply(sub), Pat::Op(_, pats) => pats.apply(sub),
} }
} }

View File

@@ -24,7 +24,7 @@ pub trait Visit<'a> {
fn visit_use(&mut self, item: &'a Use) -> Result<(), Self::Error> { fn visit_use(&mut self, item: &'a Use) -> Result<(), Self::Error> {
item.children(self) item.children(self)
} }
fn visit_pat(&mut self, item: &'a Pat) -> Result<(), Self::Error> { fn visit_pat<A: Annotation>(&mut self, item: &'a Pat<A>) -> Result<(), Self::Error> {
item.children(self) item.children(self)
} }
fn visit_bind<A: Annotation>(&mut self, item: &'a Bind<A>) -> Result<(), Self::Error> { fn visit_bind<A: Annotation>(&mut self, item: &'a Bind<A>) -> Result<(), Self::Error> {
@@ -107,18 +107,13 @@ impl<'a> Walk<'a> for Use {
} }
} }
impl<'a> Walk<'a> for Pat { impl<'a, A: Annotation> Walk<'a> for Pat<A> {
fn children<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> { fn children<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
match self { match self {
Self::Ignore | Self::Never => Ok(()), Self::Ignore | Self::Never => Ok(()),
Self::MetId(id) => id.visit_in(v), Self::MetId(id) => id.visit_in(v),
Self::Name(name) => name.visit_in(v), Self::Name(name) => name.visit_in(v),
Self::Path(path) => path.visit_in(v), Self::Value(literal) => literal.visit_in(v),
Self::NamedRecord(path, pat) | Self::NamedTuple(path, pat) => {
path.visit_in(v)?;
pat.visit_in(v)
}
Self::Lit(literal) => literal.visit_in(v),
Self::Op(_, pats) => pats.visit_in(v), Self::Op(_, pats) => pats.visit_in(v),
} }
} }

View File

@@ -257,11 +257,19 @@ impl<'t> Lexer<'t> {
/// Consumes characters until the lexer reaches a newline `'\n'` /// Consumes characters until the lexer reaches a newline `'\n'`
pub fn line_comment(&mut self) -> Result<Token, LexError> { pub fn line_comment(&mut self) -> Result<Token, LexError> {
let kind = match self.consume().peek() { let kind = match self.consume().peek() {
Some('!' | '/') => TKind::Doc, Some('/') => TKind::OutDoc,
Some('!') => TKind::InDoc,
_ => TKind::Comment, _ => TKind::Comment,
}; };
while self.consume().peek().is_some_and(|c| c != '\n') {} while self.consume().peek().is_some_and(|c| c != '\n') {}
Ok(self.produce(kind)) let (lexeme, _) = self.as_str();
let lexeme = lexeme
.strip_prefix("///")
.or_else(|| lexeme.strip_prefix("//!"))
.map(|lexeme| lexeme.strip_prefix(" ").unwrap_or(lexeme))
.unwrap_or(lexeme);
Ok(self.produce_with_lexeme(kind, Lexeme::String(lexeme.into())))
} }
/// Consumes characters until the lexer reaches the end of a *nested* block comment. /// Consumes characters until the lexer reaches the end of a *nested* block comment.

View File

@@ -8,7 +8,7 @@ use crate::{
}; };
pub trait Parse<'t> { pub trait Parse<'t> {
type Prec: Copy; type Prec: Copy + Default;
fn parse(p: &mut Parser<'t>, _level: Self::Prec) -> PResult<Self> fn parse(p: &mut Parser<'t>, _level: Self::Prec) -> PResult<Self>
where Self: Sized; where Self: Sized;
@@ -85,7 +85,10 @@ impl<'t> Parser<'t> {
if let Ok(tok) = &tok { if let Ok(tok) = &tok {
self.last_loc = tok.span; self.last_loc = tok.span;
self.elide_do = matches!(tok.kind, TKind::RCurly | TKind::Semi) self.elide_do = matches!(
tok.kind,
TKind::RCurly | TKind::Semi | TKind::DotDot | TKind::DotDotEq
)
} }
tok tok
@@ -183,9 +186,26 @@ impl<'t> Parser<'t> {
/// Consumes the currently peeked token without returning it. /// Consumes the currently peeked token without returning it.
pub fn consume(&mut self) -> &mut Self { pub fn consume(&mut self) -> &mut Self {
self.next_tok = None; if self.next_tok.as_ref().is_some_and(|tok| tok.is_ok()) {
let _ = self.take();
}
self self
} }
/// Consumes the next token, and attempts to split it into multiple.
///
/// If the next token cannot be split, it will be returned.
pub fn split(&mut self) -> PResult<Token> {
let Token { lexeme, kind, span } = self.next()?;
let kind = match kind.split() {
Err(_) => kind,
Ok((out, next)) => {
self.next_tok = Some(Ok(Token { lexeme: lexeme.clone(), kind: next, span }));
out
}
};
Ok(Token { lexeme, kind, span })
}
} }
impl<'t> Parse<'t> for Path { impl<'t> Parse<'t> for Path {

View File

@@ -1,3 +1,4 @@
use super::pat::Prec as PatPrec;
use crate::{ast::BindOp, lexer::LexError, span::Span, token::TKind}; use crate::{ast::BindOp, lexer::LexError, span::Span, token::TKind};
use std::{error::Error, fmt::Display}; use std::{error::Error, fmt::Display};
@@ -11,7 +12,7 @@ pub enum ParseError {
Expected(TKind, TKind, Span), Expected(TKind, TKind, Span),
NotLiteral(TKind, Span), NotLiteral(TKind, Span),
NotUse(TKind, Span), NotUse(TKind, Span),
NotPattern(TKind, Span), NotPattern(TKind, PatPrec, Span),
NotMatch(BindOp, BindOp, Span), NotMatch(BindOp, BindOp, Span),
NotPrefix(TKind, Span), NotPrefix(TKind, Span),
NotInfix(TKind, Span), NotInfix(TKind, Span),
@@ -30,7 +31,9 @@ impl Display for ParseError {
Self::Expected(e, tk, loc) => write!(f, "{loc}: Expected {e:?}, got {tk:?}."), Self::Expected(e, tk, loc) => write!(f, "{loc}: Expected {e:?}, got {tk:?}."),
Self::NotLiteral(tk, loc) => write!(f, "{loc}: {tk:?} is not valid in a literal."), Self::NotLiteral(tk, loc) => write!(f, "{loc}: {tk:?} is not valid in a literal."),
Self::NotUse(tk, loc) => write!(f, "{loc}: {tk:?} is no use!"), Self::NotUse(tk, loc) => write!(f, "{loc}: {tk:?} is no use!"),
Self::NotPattern(tk, loc) => write!(f, "{loc}: {tk:?} is not valid in a pattern."), Self::NotPattern(tk, prec, loc) => {
write!(f, "{loc}: {tk:?} is not valid in a {prec:?} pattern.")
}
Self::NotMatch(bk, ex, loc) => { Self::NotMatch(bk, ex, loc) => {
write!(f, "{loc}: {bk:?} is not valid in a {ex:?} expression.") write!(f, "{loc}: {bk:?} is not valid in a {ex:?} expression.")
} }
@@ -46,6 +49,7 @@ pub type PResult<T> = Result<T, ParseError>;
pub trait PResultExt<T> { pub trait PResultExt<T> {
fn no_eof(self) -> PResult<T>; fn no_eof(self) -> PResult<T>;
fn allow_eof(self) -> PResult<Option<T>>; fn allow_eof(self) -> PResult<Option<T>>;
fn is_eof(&self) -> bool;
} }
impl<T> PResultExt<T> for PResult<T> { impl<T> PResultExt<T> for PResult<T> {
@@ -62,6 +66,9 @@ impl<T> PResultExt<T> for PResult<T> {
Err(e) => Err(e), Err(e) => Err(e),
} }
} }
fn is_eof(&self) -> bool {
matches!(self, Err(ParseError::EOF(_)))
}
} }
/// Opens a scope where [`ParseError::EOF`] is unexpected (See [`PResultExt::no_eof`]) /// Opens a scope where [`ParseError::EOF`] is unexpected (See [`PResultExt::no_eof`])

View File

@@ -76,6 +76,7 @@ pub enum Ps {
Lit, // Literal Lit, // Literal
Use, // use Use Use, // use Use
Def, // any definition (let, const, static, struct, enum, fn, ...) Def, // any definition (let, const, static, struct, enum, fn, ...)
Doc, // Documentation Comment
For, // for Pat in Expr Expr else Expr For, // for Pat in Expr Expr else Expr
Lambda0, // || Expr Lambda0, // || Expr
Lambda, // | Pat,* | Expr Lambda, // | Pat,* | Expr
@@ -90,6 +91,8 @@ pub enum Ps {
/// and its [precedence level](Prec) /// and its [precedence level](Prec)
fn from_prefix(token: &Token) -> PResult<(Ps, Prec)> { fn from_prefix(token: &Token) -> PResult<(Ps, Prec)> {
Ok(match token.kind { Ok(match token.kind {
TKind::OutDoc => (Ps::Doc, Prec::Max),
TKind::InDoc => (Ps::Doc, Prec::Max),
TKind::Do => (Ps::Op(Op::Do), Prec::Do), TKind::Do => (Ps::Op(Op::Do), Prec::Do),
TKind::Semi => (Ps::End, Prec::Body), TKind::Semi => (Ps::End, Prec::Body),
@@ -207,12 +210,9 @@ impl<'t> Parse<'t> for Expr {
fn parse(p: &mut Parser<'t>, level: usize) -> PResult<Self> { fn parse(p: &mut Parser<'t>, level: usize) -> PResult<Self> {
const MIN: usize = Prec::MIN; const MIN: usize = Prec::MIN;
// TODO: in-tree doc comments
while p.next_if(TKind::Doc)?.is_ok() {}
// Prefix // Prefix
let tok @ &Token { kind, span, .. } = p.peek()?; let tok @ &Token { kind, span, .. } = p.peek()?;
let ((op, prec), span) = (from_prefix(tok)?, span); let (op, prec) = from_prefix(tok)?;
no_eof(move || { no_eof(move || {
let mut head = match op { let mut head = match op {
// "End" is produced when an "empty" expression is syntactically required. // "End" is produced when an "empty" expression is syntactically required.
@@ -227,11 +227,19 @@ impl<'t> Parse<'t> for Expr {
Ps::Lit => Expr::Lit(p.parse(())?), Ps::Lit => Expr::Lit(p.parse(())?),
Ps::Use => Expr::Use(p.consume().parse(())?), Ps::Use => Expr::Use(p.consume().parse(())?),
Ps::Def => Expr::Bind(p.parse(None)?), Ps::Def => Expr::Bind(p.parse(None)?),
Ps::Doc => {
let comment = Literal::Str(p.take_lexeme()?.string().unwrap());
let comment = Expr::Lit(comment).anno(span);
let next = p.parse(level)?;
Expr::Op(Op::Meta, vec![comment, next])
}
Ps::For => parse_for(p, ())?,
Ps::Lambda | Ps::Lambda0 => { Ps::Lambda | Ps::Lambda0 => {
p.consume(); p.consume();
let args = if op == Ps::Lambda { let args = if op == Ps::Lambda {
p.opt(PPrec::Tuple, TKind::Bar)? p.opt(PPrec::Tuple, TKind::Bar)?
.map(Pat::to_tuple)
.unwrap_or(Pat::Op(PatOp::Tuple, vec![])) .unwrap_or(Pat::Op(PatOp::Tuple, vec![]))
} else { } else {
Pat::Op(PatOp::Tuple, vec![]) Pat::Op(PatOp::Tuple, vec![])
@@ -246,7 +254,6 @@ impl<'t> Parse<'t> for Expr {
vec![p.parse(Prec::Body.next())?], vec![p.parse(Prec::Body.next())?],
))) )))
} }
Ps::For => parse_for(p, ())?,
Ps::Op(Op::Match) => parse_match(p)?, Ps::Op(Op::Match) => parse_match(p)?,
Ps::Op(Op::Meta) => Expr::Op( Ps::Op(Op::Meta) => Expr::Op(
Op::Meta, Op::Meta,
@@ -260,10 +267,10 @@ impl<'t> Parse<'t> for Expr {
), ),
Ps::Op(Op::Block) => Expr::Op( Ps::Op(Op::Block) => Expr::Op(
Op::Block, Op::Block,
p.consume().opt(MIN, TKind::RCurly)?.into_iter().collect(), p.consume().opt(MIN, kind.flip())?.into_iter().collect(),
), ),
Ps::Op(Op::Array) => parse_array(p)?, Ps::Op(Op::Array) => parse_array(p)?,
Ps::Op(Op::Group) => match p.consume().opt(MIN, TKind::RParen)? { Ps::Op(Op::Group) => match p.consume().opt(MIN, kind.flip())? {
Some(value) => Expr::Op(Op::Group, vec![value]), Some(value) => Expr::Op(Op::Group, vec![value]),
None => Expr::Op(Op::Tuple, vec![]), None => Expr::Op(Op::Tuple, vec![]),
}, },
@@ -295,12 +302,11 @@ impl<'t> Parse<'t> for Expr {
}; };
// Infix and Postfix // Infix and Postfix
while let Ok(Some(tok)) = p.peek().allow_eof() while let Ok(Some(tok @ &Token { kind, .. })) = p.peek().allow_eof()
&& let Ok((op, prec)) = from_infix(tok) && let Ok((op, prec)) = from_infix(tok)
&& level <= prec.prev() && level <= prec.prev()
&& op != Ps::End && op != Ps::End
{ {
let kind = tok.kind;
let span = span.merge(p.span()); let span = span.merge(p.span());
head = match op { head = match op {
@@ -320,7 +326,7 @@ impl<'t> Parse<'t> for Expr {
span, span,
match p.consume().peek().allow_eof()? { match p.consume().peek().allow_eof()? {
Some(_) => p.parse(prec.next())?, Some(_) => p.parse(prec.next())?,
None => Anno(Default::default(), span), None => Anno(Expr::Omitted, span),
}, },
), ),
Ps::Op(Op::Index) => Expr::Op( Ps::Op(Op::Index) => Expr::Op(
@@ -328,15 +334,14 @@ impl<'t> Parse<'t> for Expr {
p.consume() p.consume()
.list(vec![head.anno(span)], 0, TKind::Comma, TKind::RBrack)?, .list(vec![head.anno(span)], 0, TKind::Comma, TKind::RBrack)?,
), ),
Ps::Op(Op::Call) => Expr::Op( Ps::Op(Op::Call) => {
Op::Call, let head = head.anno(span);
vec![ let args = match p.consume().opt::<Anno<Expr>>(0, TKind::RParen)? {
head.anno(span), None => Expr::Op(Op::Tuple, vec![]).anno(span),
p.consume() Some(Anno(expr, span)) => expr.to_tuple(span).anno(span),
.opt(0, TKind::RParen)? };
.unwrap_or_else(|| Expr::Op(Op::Tuple, vec![]).anno(span)), Expr::Op(Op::Call, vec![head, args])
], }
),
Ps::Op(op @ (Op::Tuple | Op::Dot | Op::LogAnd | Op::LogOr)) => Expr::Op( Ps::Op(op @ (Op::Tuple | Op::Dot | Op::LogAnd | Op::LogOr)) => Expr::Op(
op, op,
p.consume() p.consume()
@@ -445,7 +450,7 @@ fn from_bind(p: &mut Parser<'_>) -> PResult<(BindOp, PPrec, Option<TKind>, Optio
TKind::Enum => (BindOp::Enum, PPrec::Tuple, None, None, None), TKind::Enum => (BindOp::Enum, PPrec::Tuple, None, None, None),
TKind::Fn => (BindOp::Fn, PPrec::Fn, None, Some(Prec::Body), None), TKind::Fn => (BindOp::Fn, PPrec::Fn, None, Some(Prec::Body), None),
TKind::Mod => (BindOp::Mod, PPrec::Max, None, Some(Prec::Body), None), TKind::Mod => (BindOp::Mod, PPrec::Max, None, Some(Prec::Body), None),
TKind::Impl => (BindOp::Impl, PPrec::Max, None, Some(Prec::Body), None), TKind::Impl => (BindOp::Impl, PPrec::Fn, None, Some(Prec::Body), None),
TKind::Bar => (BindOp::Match, PPrec::Alt, Some(TKind::FatArrow), Some(Prec::Body), None), TKind::Bar => (BindOp::Match, PPrec::Alt, Some(TKind::FatArrow), Some(Prec::Body), None),
// no consume! // no consume!
_ => return Ok((BindOp::Match, PPrec::Alt, Some(TKind::FatArrow), Some(Prec::Body), None)), _ => return Ok((BindOp::Match, PPrec::Alt, Some(TKind::FatArrow), Some(Prec::Body), None)),
@@ -481,12 +486,15 @@ impl<'t> Parse<'t> for Bind {
return Ok(Self(level, generics, pat, vec![])); return Ok(Self(level, generics, pat, vec![]));
}; };
// `=>` for match, `=`? for everything else // `=>` for match, `=` for `let`, `type`
if let Some(arrow) = arrow if let Some(arrow) = arrow {
&& p.next_if(arrow).allow_eof()?.is_none_or(|v| v.is_err()) if p.next_if(arrow).allow_eof()?.is_none_or(|v| v.is_err()) {
{
return Ok(Self(level, generics, pat, vec![])); return Ok(Self(level, generics, pat, vec![]));
} }
} else {
// Allow prefix `=`? for the rest of them
p.next_if(TKind::Eq).allow_eof()?;
}
// `=` Expr // `=` Expr
let body = p.parse(bodyp.value())?; let body = p.parse(bodyp.value())?;

View File

@@ -1,4 +1,4 @@
use super::{PResult, PResultExt, Parse, ParseError, Parser}; use super::{PResult, PResultExt, Parse, ParseError, Parser, expr::Prec as ExPrec};
use crate::{ use crate::{
ast::*, ast::*,
token::{TKind, Token}, token::{TKind, Token},
@@ -8,9 +8,10 @@ use crate::{
/// ///
/// Lower (toward [Prec::Min]) precedence levels can contain /// Lower (toward [Prec::Min]) precedence levels can contain
/// all higher (toward [Prec::Max]) precedence levels. /// all higher (toward [Prec::Max]) precedence levels.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum Prec { pub enum Prec {
/// The lowest precedence /// The lowest precedence
#[default]
Min, Min,
/// "Alternate" pattern: `Pat | Pat` /// "Alternate" pattern: `Pat | Pat`
Alt, Alt,
@@ -18,7 +19,7 @@ pub enum Prec {
Tuple, Tuple,
/// Type annotation: `Pat : Pat` /// Type annotation: `Pat : Pat`
Typed, Typed,
/// Function pattern: `Pat -> Pat` /// Function signature: `foo(bar: baz, ..) -> qux`
Fn, Fn,
/// Range pattern: `Pat .. Pat`, `Pat ..= Pat` /// Range pattern: `Pat .. Pat`, `Pat ..= Pat`
Range, Range,
@@ -51,8 +52,52 @@ impl Prec {
} }
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Prefix {
/// Consume (and disregard) this prefix token
Consume,
Underscore,
Never,
MetId,
Id,
Array,
Constant,
Op(PatOp),
Split(PatOp),
}
fn from_prefix(token: &Token) -> PResult<(Prefix, Prec)> {
Ok(match token.kind {
TKind::True
| TKind::False
| TKind::Character
| TKind::Integer
| TKind::String
| TKind::Minus
| TKind::Const => (Prefix::Constant, Prec::Max),
TKind::Identifier if token.lexeme.str() == Some("_") => (Prefix::Underscore, Prec::Max),
TKind::ColonColon | TKind::Identifier => (Prefix::Id, Prec::Max),
TKind::Bang => (Prefix::Never, Prec::Max),
TKind::Amp => (Prefix::Op(PatOp::Ref), Prec::Max),
TKind::AmpAmp => (Prefix::Split(PatOp::Ref), Prec::Max),
TKind::Star => (Prefix::Op(PatOp::Ptr), Prec::Max),
TKind::Mut => (Prefix::Op(PatOp::Mut), Prec::Max),
TKind::Pub => (Prefix::Op(PatOp::Pub), Prec::Max),
TKind::Grave => (Prefix::MetId, Prec::Max),
TKind::Fn => (Prefix::Op(PatOp::Fn), Prec::Fn),
TKind::Bar => (Prefix::Consume, Prec::Alt),
TKind::DotDot => (Prefix::Op(PatOp::Rest), Prec::Max),
TKind::DotDotEq => (Prefix::Op(PatOp::RangeIn), Prec::Max),
TKind::LCurly => (Prefix::Op(PatOp::Record), Prec::Typed),
TKind::LParen => (Prefix::Op(PatOp::Tuple), Prec::Fn),
TKind::LBrack => (Prefix::Array, Prec::Max),
kind => Err(ParseError::NotPrefix(kind, token.span))?,
})
}
/// Tries to map the incoming Token to a [pattern operator](PatOp) /// Tries to map the incoming Token to a [pattern operator](PatOp)
/// and its [precedence level](Prec) /// and its following [precedence level](Prec)
fn from_infix(token: &Token) -> Option<(PatOp, Prec)> { fn from_infix(token: &Token) -> Option<(PatOp, Prec)> {
Some(match token.kind { Some(match token.kind {
TKind::Arrow => (PatOp::Fn, Prec::Fn), TKind::Arrow => (PatOp::Fn, Prec::Fn),
@@ -61,6 +106,10 @@ fn from_infix(token: &Token) -> Option<(PatOp, Prec)> {
TKind::Comma => (PatOp::Tuple, Prec::Tuple), TKind::Comma => (PatOp::Tuple, Prec::Tuple),
TKind::DotDot => (PatOp::RangeEx, Prec::Range), TKind::DotDot => (PatOp::RangeEx, Prec::Range),
TKind::DotDotEq => (PatOp::RangeIn, Prec::Range), TKind::DotDotEq => (PatOp::RangeIn, Prec::Range),
TKind::Lt => (PatOp::Generic, Prec::Fn),
TKind::LCurly => (PatOp::TypePrefixed, Prec::Typed),
// LParen is used in function signatures
TKind::LParen => (PatOp::TypePrefixed, Prec::Fn),
_ => None?, _ => None?,
}) })
} }
@@ -69,99 +118,70 @@ impl<'t> Parse<'t> for Pat {
type Prec = Prec; type Prec = Prec;
fn parse(p: &mut Parser<'t>, level: Prec) -> PResult<Self> { fn parse(p: &mut Parser<'t>, level: Prec) -> PResult<Self> {
let tok = p.peek()?; let tok @ &Token { kind, span, .. } = p.peek()?;
let (op, prec) = from_prefix(tok)?;
if level > prec {
return Err(ParseError::NotPattern(kind, level, span));
}
// Prefix let mut head = match op {
let mut head = match tok.kind { Prefix::Consume => p.consume().parse(level)?,
TKind::True | TKind::False | TKind::Character | TKind::Integer | TKind::String => { Prefix::Underscore => p.consume().then(Pat::Ignore),
Pat::Lit(p.parse(())?) Prefix::Never => p.consume().then(Pat::Never),
} Prefix::MetId => Pat::MetId(p.consume().next()?.lexeme.to_string()),
TKind::Bar => p.consume().parse(level)?, Prefix::Constant => Pat::Value(p.parse(ExPrec::Unary.value())?),
TKind::Bang => p.consume().then(Pat::Never), Prefix::Array => parse_array_pat(p)?,
TKind::Fn => Pat::Op(PatOp::Fn, vec![p.consume().parse(Prec::Typed)?]), Prefix::Id => {
TKind::Amp => Pat::Op(PatOp::Ref, vec![p.consume().parse(Prec::Max)?]), let Anno(mut path, span): Anno<Path> = p.parse(())?;
TKind::Star => Pat::Op(PatOp::Ptr, vec![p.consume().parse(Prec::Max)?]),
TKind::Mut => Pat::Op(PatOp::Mut, vec![p.consume().parse(Prec::Max)?]),
TKind::Pub => Pat::Op(PatOp::Pub, vec![p.consume().parse(Prec::Max)?]),
TKind::AmpAmp => Pat::Op(
PatOp::Ref,
vec![Pat::Op(PatOp::Ref, vec![p.consume().parse(Prec::Max)?])],
),
TKind::Identifier => match tok.lexeme.str() {
Some("_") => p.consume().then(Pat::Ignore),
_ => {
let mut path: Path = p.parse(())?;
// TODO: make these postfix. // TODO: make these postfix.
match p.peek().map(Token::kind) { match path.parts.len() {
Ok(TKind::LParen) => Pat::NamedTuple(path, p.parse(Prec::Typed)?), 1 => Pat::Name(path.parts.pop().expect("name has 1 part")),
Ok(TKind::LCurly) if level <= Prec::Tuple.next() => Pat::NamedRecord( _ => Pat::Value(Box::new(Anno(Expr::Id(path), span))),
path,
p.consume()
.opt(Prec::Tuple, TKind::RCurly)?
.unwrap_or_else(|| Box::new(Pat::Op(PatOp::Tuple, vec![]))),
),
Ok(_) | Err(ParseError::EOF(_)) => match path.parts.len() {
1 => Self::Name(path.parts.pop().expect("name has 1 part")),
_ => Self::Path(path),
},
Err(e) => Err(e)?,
} }
} }
}, Prefix::Op(op @ (PatOp::Record | PatOp::Tuple)) => Pat::Op(
TKind::Grave => Pat::MetId(p.consume().next()?.lexeme.to_string()), op,
TKind::DotDot => Pat::Op(
PatOp::Rest,
// Identifier in Rest position always becomes binder
match p.consume().peek().allow_eof()?.map(Token::kind) {
Some(TKind::Identifier) => vec![Pat::Name(
p.take_lexeme()
.expect("should have lexeme")
.string()
.expect("should be string"),
)],
Some(TKind::Grave | TKind::Integer | TKind::Character) => vec![p.parse(level)?],
_ => vec![],
},
),
TKind::DotDotEq => Pat::Op(
PatOp::RangeIn,
match p.consume().peek().allow_eof()?.map(Token::kind) {
Some(TKind::Grave | TKind::Integer | TKind::Character) => vec![p.parse(level)?],
_ => vec![],
},
),
TKind::LCurly => Pat::Op(
PatOp::Record,
p.consume() p.consume()
.list(vec![], Prec::Typed, TKind::Comma, TKind::RCurly)?, .list(vec![], Prec::Tuple.next(), TKind::Comma, kind.flip())?,
), ),
TKind::LParen => Pat::Op( Prefix::Op(op @ (PatOp::Rest | PatOp::RangeEx | PatOp::RangeIn)) => {
PatOp::Tuple, // next token must continue a pattern
p.consume() match p.consume().peek().allow_eof()? {
.list(vec![], Prec::Typed, TKind::Comma, TKind::RParen)?, Some(tok) if from_prefix(tok).is_ok() => Pat::Op(op, vec![p.parse(prec)?]),
), _ => Pat::Op(op, vec![]),
TKind::LBrack => parse_array_pat(p)?, }
_ => Err(ParseError::NotPattern(tok.kind, tok.span))?, }
Prefix::Op(op) => Pat::Op(op, vec![p.consume().parse(prec)?]),
Prefix::Split(op) => {
p.split()?;
Pat::Op(op, vec![p.parse(prec)?])
}
}; };
while let Ok(Some(tok)) = p.peek().allow_eof() while let Ok(Some(tok @ &Token { kind, .. })) = p.peek().allow_eof()
&& let Some((op, prec)) = from_infix(tok) && let Some((op, prec)) = from_infix(tok)
&& level <= prec && level <= prec
{ {
let kind = tok.kind;
head = match op { head = match op {
PatOp::Typed => Pat::Op(op, vec![head, p.consume().parse(prec.next())?]),
PatOp::Fn => Pat::Op(op, vec![head, p.consume().parse(Prec::Fn)?]),
PatOp::RangeEx | PatOp::RangeIn => Pat::Op( PatOp::RangeEx | PatOp::RangeIn => Pat::Op(
op, op,
match p.consume().peek().map(Token::kind) { if let Some(tok) = p.consume().peek().allow_eof()?
Ok(TKind::Integer | TKind::Character | TKind::Identifier) => { && from_prefix(tok).is_ok()
vec![head, p.parse(prec.next())?] {
} vec![head, p.parse(prec)?]
_ => vec![head], } else {
vec![head]
}, },
), ),
_ => Pat::Op(op, p.consume().list_bare(vec![head], prec.next(), kind)?), PatOp::Generic => Pat::Op(
op,
p.consume()
.list(vec![head], prec, TKind::Comma, kind.flip())?,
),
PatOp::TypePrefixed => Pat::Op(op, vec![head, p.parse(prec)?]),
PatOp::Tuple => Pat::Op(op, p.consume().list_bare(vec![head], prec.next(), kind)?),
PatOp::Fn => Pat::Op(op, vec![head, p.consume().parse(prec)?]),
_ => Pat::Op(op, vec![head, p.consume().parse(prec.next())?]),
} }
} }
Ok(head) Ok(head)

View File

@@ -66,8 +66,12 @@ impl std::fmt::Display for Lexeme {
/// The lexical classification of a [Token]. /// The lexical classification of a [Token].
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TKind { pub enum TKind {
Comment, // Line or block comment /// Line or block comment
Doc, // Doc comment Comment,
/// Outer doc comment ///.*
OutDoc,
/// Inner doc comment: //!.*
InDoc,
As, As,
Break, Break,
@@ -100,59 +104,165 @@ pub enum TKind {
Identifier, // or Keyword Identifier, // or Keyword
Character, Character,
String, String,
Integer, // 0(x[0-9A-Fa-f]* | d[0-9]* | o[0-7]* | b[0-1]*) | [1-9][0-9]* /// `0(x[0-9A-Fa-f]* | d[0-9]* | o[0-7]* | b[0-1]*) | [1-9][0-9]*`
LCurly, // { Integer,
RCurly, // } /// {
LBrack, // [ LCurly,
RBrack, // ] /// }
LParen, // ( RCurly,
RParen, // ) /// [
Amp, // & LBrack,
AmpAmp, // && /// ]
AmpEq, // &= RBrack,
Arrow, // -> /// (
At, // @ LParen,
Backslash, // \ /// )
Bang, // ! RParen,
BangBang, // !! /// &
BangEq, // != Amp,
Bar, // | /// &&
BarBar, // || AmpAmp,
BarEq, // |= /// &=
Colon, // : AmpEq,
ColonColon, // :: /// ->
Comma, // , Arrow,
Dot, // . /// @
DotDot, // .. At,
DotDotEq, // ..= /// \
Eq, // = Backslash,
EqEq, // == /// !
FatArrow, // => Bang,
Grave, // ` /// !!
Gt, // > BangBang,
GtEq, // >= /// !=
GtGt, // >> BangEq,
GtGtEq, // >>= /// |
Hash, // # Bar,
HashBang, // #! /// ||
Lt, // < BarBar,
LtEq, // <= /// |=
LtLt, // << BarEq,
LtLtEq, // <<= /// :
Minus, // - Colon,
MinusEq, // -= /// ::
Plus, // + ColonColon,
PlusEq, // += /// ,
Question, // ? Comma,
Rem, // % /// .
RemEq, // %= Dot,
Semi, // ; /// ..
Slash, // / DotDot,
SlashEq, // /= /// ..=
Star, // * DotDotEq,
StarEq, // *= /// =
Tilde, // ~ Eq,
Xor, // ^ /// ==
XorEq, // ^= EqEq,
XorXor, // ^^ /// =>
FatArrow,
/// \`
Grave,
/// >
Gt,
/// >=
GtEq,
/// >>
GtGt,
/// >>=
GtGtEq,
/// #
Hash,
/// #!
HashBang,
/// <
Lt,
/// <=
LtEq,
/// <<
LtLt,
/// <<=
LtLtEq,
/// -
Minus,
/// -=
MinusEq,
/// +
Plus,
/// +=
PlusEq,
/// ?
Question,
/// %
Rem,
/// %=
RemEq,
/// ;
Semi,
/// /
Slash,
/// /=
SlashEq,
/// *
Star,
/// *=
StarEq,
/// ~
Tilde,
/// ^
Xor,
/// ^=
XorEq,
/// ^^
XorXor,
}
impl TKind {
pub const fn flip(self) -> Self {
match self {
Self::LCurly => Self::RCurly,
Self::RCurly => Self::LCurly,
Self::LBrack => Self::RBrack,
Self::RBrack => Self::LBrack,
Self::LParen => Self::RParen,
Self::RParen => Self::LParen,
Self::Gt => Self::Lt,
Self::GtGt => Self::LtLt,
Self::LtLt => Self::GtGt,
Self::Lt => Self::Gt,
_ => self,
}
}
/// Splits a single [TKind] into two, if possible, or returns the original.
pub const fn split(self) -> Result<(Self, Self), Self> {
Ok(match self {
Self::AmpAmp => (Self::Amp, Self::Amp),
Self::AmpEq => (Self::Amp, Self::Eq),
Self::Arrow => (Self::Minus, Self::Gt),
Self::BangBang => (Self::Bang, Self::Bang),
Self::BangEq => (Self::Bang, Self::Eq),
Self::BarBar => (Self::Bar, Self::Bar),
Self::BarEq => (Self::Bar, Self::Eq),
Self::ColonColon => (Self::Colon, Self::Colon),
Self::DotDot => (Self::Dot, Self::Dot),
Self::DotDotEq => (Self::DotDot, Self::Eq),
Self::EqEq => (Self::Eq, Self::Eq),
Self::FatArrow => (Self::Eq, Self::Gt),
Self::GtEq => (Self::Gt, Self::Eq),
Self::GtGt => (Self::Gt, Self::Gt),
Self::GtGtEq => (Self::Gt, Self::GtEq),
Self::HashBang => (Self::Hash, Self::Bang),
Self::LtEq => (Self::Lt, Self::Eq),
Self::LtLt => (Self::Lt, Self::Lt),
Self::LtLtEq => (Self::Lt, Self::LtEq),
Self::MinusEq => (Self::Minus, Self::Eq),
Self::PlusEq => (Self::Plus, Self::Eq),
Self::RemEq => (Self::Rem, Self::Eq),
Self::SlashEq => (Self::Slash, Self::Eq),
Self::StarEq => (Self::Star, Self::Eq),
Self::XorEq => (Self::Xor, Self::Eq),
Self::XorXor => (Self::Xor, Self::Xor),
_ => return Err(self),
})
}
} }