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)
}
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;
item.children(self)
}

View File

@@ -54,6 +54,8 @@ impl ToLisp {
PatOp::Slice => "slice",
PatOp::ArRep => "ar-rep",
PatOp::Typed => "typed",
PatOp::TypePrefixed => "type-prefixed",
PatOp::Generic => "generic-in",
PatOp::Fn => "fn",
PatOp::Alt => "alt",
}
@@ -196,28 +198,13 @@ impl<'a> Visit<'a> for ToLisp {
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 {
Pat::Ignore => print!("(ignore)"),
Pat::Never => print!("(never)"),
Pat::MetId(id) => print!("(replace {id})"),
Pat::Name(name) => print!("{name}"),
Pat::Path(path) => path.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::Value(literal) => literal.visit_in(self)?,
Pat::Op(pat_op, pats) => {
print!("({}", self.pat_op(*pat_op));
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 {
match expr {
ExprAtom(value) => value,
ExprOp('*', [lhs, rhs]) => execute(lhs) * execute(rhs),
ExprOp('/', [lhs, rhs]) => execute(lhs) / execute(rhs),
ExprOp('%', [lhs, rhs]) => execute(lhs) % execute(rhs),
ExprOp('+', [lhs, rhs]) => execute(lhs) + execute(rhs),
ExprOp('-', [lhs, rhs]) => execute(lhs) - execute(rhs),
// ExprOp('>', [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,
ExprOp('-', [lhs]) => - execute(lhs),
Expr::Atom(value) => value,
Expr::Op('*', [lhs, rhs]) => execute(lhs) * execute(rhs),
Expr::Op('/', [lhs, rhs]) => execute(lhs) / execute(rhs),
Expr::Op('%', [lhs, rhs]) => execute(lhs) % execute(rhs),
Expr::Op('+', [lhs, rhs]) => execute(lhs) + execute(rhs),
Expr::Op('-', [lhs, rhs]) => execute(lhs) - execute(rhs),
// Expr::Op('>', [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,
Expr::Op('-', [lhs]) => - execute(lhs),
other => {
panic("Unknown operation: " + fmt(other))
}
@@ -25,9 +25,9 @@ fn execute(expr: Expr) -> f64 {
/// Formats an expression to a string
fn fmt_expr(expr: Expr) -> str {
match expr {
ExprAtom(value) => fmt(value),
ExprOp(operator, [lhs, rhs]) => fmt('(', fmt_expr(lhs), ' ', operator, ' ', fmt_expr(rhs), ')'),
ExprOp(operator, [rhs]) => fmt(operator, fmt_expr(rhs)),
Expr::Atom(value) => fmt(value),
Expr::Op(operator, [lhs, rhs]) => fmt('(', fmt_expr(lhs), ' ', operator, ' ', fmt_expr(rhs), ')'),
Expr::Op(operator, [rhs]) => fmt(operator, fmt_expr(rhs)),
_ => 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)
#[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 ]
/// `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,
Try, // Expr '?'
Index, // Expr [ Expr,* ]
Call, // Expr ( Expr,* )
/// `Expr '?'`
Try,
/// `Expr [ Expr,* ]`
Index,
/// `Expr ( Expr,* )`
Call,
Pub, // pub Expr
Const, // const Expr
Static, // static Expr
Loop, // loop Expr
Match, // match Expr { <Bind(Match, ..)>,* }
If, // if Expr Expr (else Expr)?
While, // while Expr Expr (else Expr)?
Break, // break Expr
Return, // return Expr
Continue, // continue
/// `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,
Dot, // Expr . Expr
/// `Expr . Expr`
Dot,
RangeEx, // Expr? ..Expr
RangeIn, // Expr? ..=Expr
Neg, // -Expr
Not, // !Expr
Identity, // !!Expr
Refer, // &Expr
Deref, // *Expr
/// `Expr? ..Expr`
RangeEx,
/// `Expr? ..=Expr`
RangeIn,
/// `-Expr`
Neg,
/// `!Expr`
Not,
/// `!!Expr`
Identity,
/// `&Expr`
Refer,
/// `*Expr`
Deref,
Mul, // Expr * Expr
Div, // Expr / Expr
Rem, // Expr % Expr
/// `Expr * Expr`
Mul,
/// `Expr / Expr`
Div,
/// `Expr % Expr`
Rem,
Add, // Expr + Expr
Sub, // Expr - Expr
/// `Expr + Expr`
Add,
/// `Expr - Expr`
Sub,
Shl, // Expr << Expr
Shr, // Expr >> Expr
/// `Expr << Expr`
Shl,
/// `Expr >> Expr`
Shr,
And, // Expr & Expr
Xor, // Expr ^ Expr
Or, // Expr | Expr
/// `Expr & Expr`
And,
/// `Expr ^ Expr`
Xor,
/// `Expr | Expr`
Or,
Lt, // Expr < Expr
Leq, // Expr <= Expr
Eq, // Expr == Expr
Neq, // Expr != Expr
Geq, // Expr >= Expr
Gt, // Expr > Expr
/// `Expr < Expr`
Lt,
/// `Expr <= Expr`
Leq,
/// `Expr == Expr`
Eq,
/// `Expr != Expr`
Neq,
/// `Expr >= Expr`
Geq,
/// `Expr > Expr`
Gt,
LogAnd, // Expr && Expr
LogXor, // Expr ^^ Expr
LogOr, // Expr || Expr
/// `Expr && Expr`
LogAnd,
/// `Expr ^^ Expr`
LogXor,
/// `Expr || Expr`
LogOr,
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
/// `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
@@ -181,11 +240,11 @@ pub enum Use {
/// ```ignore
/// let Pat (= Expr (else Expr)?)?
/// type Pat (= Expr)?
/// struct Pat
/// enum Pat
/// fn Pat Expr
/// mod Pat Expr
/// impl Pat Expr
/// struct Pat
/// enum Pat
/// for Pat in Expr Expr (else Expr)?
/// Pat => Expr // in match
/// ```
@@ -193,7 +252,7 @@ pub enum Use {
pub struct Bind<A: Annotation = Span>(
pub BindOp,
pub Vec<Path>,
pub Pat,
pub Pat<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.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Pat {
pub enum Pat<A: Annotation = Span> {
/// Matches anything without binding
Ignore,
/// Matches nothing, ever
@@ -246,16 +305,10 @@ pub enum Pat {
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 }`
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 value by equality comparison
Value(Box<Anno<Expr<A>, A>>),
/// Matches a compound pattern
Op(PatOp, Vec<Pat>),
Op(PatOp, Vec<Pat<A>>),
}
/// Operators on lists of patterns
@@ -285,6 +338,10 @@ pub enum PatOp {
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
@@ -321,6 +378,13 @@ impl<A: Annotation> Expr<A> {
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,
@@ -328,6 +392,10 @@ impl<A: Annotation> Expr<A> {
)
}
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 {
@@ -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 {
fn from(value: &str) -> Self {
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() {
[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}"),
},
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 {
Pat::NamedRecord(name, bind) => match bind.as_ref() {
Pat::Op(PatOp::Tuple, parts) => f
Pat::Op(PatOp::TypePrefixed, bind) => match bind.as_slice() {
[name, Pat::Op(PatOp::Record, parts)] => f
.delimit_indented(fmt!("{name} {{"), "}")
.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),
},
@@ -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 {
match self {
Self::Ignore => "_".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::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::Tuple, 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),
},
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() {
[] => op.fmt(f),
[rest] => write!(f, "{op}{rest}"),
@@ -297,6 +303,8 @@ impl Display for PatOp {
Self::Slice => ", ",
Self::ArRep => "[;]",
Self::Typed => ": ",
Self::Generic => "T<>",
Self::TypePrefixed => "T()",
Self::Fn => " -> ",
Self::Alt => " | ",
})

View File

@@ -45,7 +45,7 @@ pub trait Fold<A: Annotation> {
}
/// 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)
}
@@ -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> {
folder.fold_pat(self)
}
@@ -149,14 +149,7 @@ impl<A: Annotation> Foldable<A> for Pat {
Self::Never => Self::Never,
Self::MetId(name) => Self::MetId(name.fold_in(folder)?),
Self::Name(name) => Self::Name(name.fold_in(folder)?),
Self::Path(path) => Self::Path(path.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::Value(expr) => Self::Value(expr.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)]
pub struct Subst<A: Annotation> {
pub exp: HashMap<String, Expr<A>>,
pub pat: HashMap<String, Pat>,
pub pat: HashMap<String, Pat<A>>,
}
impl<A: Annotation> Default for Subst<A> {
@@ -16,7 +16,7 @@ impl<A: Annotation> Default for 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) {
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 {
match (pat, expr) {
(Pat::MetId(name), _) if name == "_" => true,
@@ -172,14 +172,8 @@ impl<A: Annotation> Match<A> for Pat {
(Pat::Never, _) => false,
(Pat::Name(pat), Pat::Name(expr)) => pat == expr,
(Pat::Name(_), _) => false,
(Pat::Path(_), Pat::Path(_)) => true,
(Pat::Path(_), _) => 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::Value(pat), Pat::Value(expr)) => pat == expr,
(Pat::Value(_), _) => false,
(Pat::Op(_, pat), Pat::Op(_, expr)) => Match::recurse(sub, pat, expr),
(Pat::Op(..), _) => false,
}
@@ -187,14 +181,13 @@ impl<A: Annotation> Match<A> for Pat {
fn apply(&mut self, sub: &Subst<A>) {
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) => {
if let Some(expr) = sub.pat.get(id) {
*self = expr.clone();
}
}
Pat::NamedRecord(_, expr) => expr.apply(sub),
Pat::NamedTuple(_, expr) => expr.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> {
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)
}
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> {
match self {
Self::Ignore | Self::Never => Ok(()),
Self::MetId(id) => id.visit_in(v),
Self::Name(name) => name.visit_in(v),
Self::Path(path) => path.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::Value(literal) => literal.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'`
pub fn line_comment(&mut self) -> Result<Token, LexError> {
let kind = match self.consume().peek() {
Some('!' | '/') => TKind::Doc,
Some('/') => TKind::OutDoc,
Some('!') => TKind::InDoc,
_ => TKind::Comment,
};
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.

View File

@@ -8,7 +8,7 @@ use crate::{
};
pub trait Parse<'t> {
type Prec: Copy;
type Prec: Copy + Default;
fn parse(p: &mut Parser<'t>, _level: Self::Prec) -> PResult<Self>
where Self: Sized;
@@ -85,7 +85,10 @@ impl<'t> Parser<'t> {
if let Ok(tok) = &tok {
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
@@ -183,9 +186,26 @@ impl<'t> Parser<'t> {
/// Consumes the currently peeked token without returning it.
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
}
/// 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 {

View File

@@ -1,3 +1,4 @@
use super::pat::Prec as PatPrec;
use crate::{ast::BindOp, lexer::LexError, span::Span, token::TKind};
use std::{error::Error, fmt::Display};
@@ -11,7 +12,7 @@ pub enum ParseError {
Expected(TKind, TKind, Span),
NotLiteral(TKind, Span),
NotUse(TKind, Span),
NotPattern(TKind, Span),
NotPattern(TKind, PatPrec, Span),
NotMatch(BindOp, BindOp, Span),
NotPrefix(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::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::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) => {
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> {
fn no_eof(self) -> PResult<T>;
fn allow_eof(self) -> PResult<Option<T>>;
fn is_eof(&self) -> bool;
}
impl<T> PResultExt<T> for PResult<T> {
@@ -62,6 +66,9 @@ impl<T> PResultExt<T> for PResult<T> {
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`])

View File

@@ -76,6 +76,7 @@ pub enum Ps {
Lit, // Literal
Use, // use Use
Def, // any definition (let, const, static, struct, enum, fn, ...)
Doc, // Documentation Comment
For, // for Pat in Expr Expr else Expr
Lambda0, // || Expr
Lambda, // | Pat,* | Expr
@@ -90,6 +91,8 @@ pub enum Ps {
/// and its [precedence level](Prec)
fn from_prefix(token: &Token) -> PResult<(Ps, Prec)> {
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::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> {
const MIN: usize = Prec::MIN;
// TODO: in-tree doc comments
while p.next_if(TKind::Doc)?.is_ok() {}
// Prefix
let tok @ &Token { kind, span, .. } = p.peek()?;
let ((op, prec), span) = (from_prefix(tok)?, span);
let (op, prec) = from_prefix(tok)?;
no_eof(move || {
let mut head = match op {
// "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::Use => Expr::Use(p.consume().parse(())?),
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 => {
p.consume();
let args = if op == Ps::Lambda {
p.opt(PPrec::Tuple, TKind::Bar)?
.map(Pat::to_tuple)
.unwrap_or(Pat::Op(PatOp::Tuple, vec![]))
} else {
Pat::Op(PatOp::Tuple, vec![])
@@ -246,7 +254,6 @@ impl<'t> Parse<'t> for Expr {
vec![p.parse(Prec::Body.next())?],
)))
}
Ps::For => parse_for(p, ())?,
Ps::Op(Op::Match) => parse_match(p)?,
Ps::Op(Op::Meta) => Expr::Op(
Op::Meta,
@@ -260,10 +267,10 @@ impl<'t> Parse<'t> for Expr {
),
Ps::Op(Op::Block) => Expr::Op(
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::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]),
None => Expr::Op(Op::Tuple, vec![]),
},
@@ -295,12 +302,11 @@ impl<'t> Parse<'t> for Expr {
};
// 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)
&& level <= prec.prev()
&& op != Ps::End
{
let kind = tok.kind;
let span = span.merge(p.span());
head = match op {
@@ -320,7 +326,7 @@ impl<'t> Parse<'t> for Expr {
span,
match p.consume().peek().allow_eof()? {
Some(_) => p.parse(prec.next())?,
None => Anno(Default::default(), span),
None => Anno(Expr::Omitted, span),
},
),
Ps::Op(Op::Index) => Expr::Op(
@@ -328,15 +334,14 @@ impl<'t> Parse<'t> for Expr {
p.consume()
.list(vec![head.anno(span)], 0, TKind::Comma, TKind::RBrack)?,
),
Ps::Op(Op::Call) => Expr::Op(
Op::Call,
vec![
head.anno(span),
p.consume()
.opt(0, TKind::RParen)?
.unwrap_or_else(|| Expr::Op(Op::Tuple, vec![]).anno(span)),
],
),
Ps::Op(Op::Call) => {
let head = head.anno(span);
let args = match p.consume().opt::<Anno<Expr>>(0, TKind::RParen)? {
None => Expr::Op(Op::Tuple, vec![]).anno(span),
Some(Anno(expr, span)) => expr.to_tuple(span).anno(span),
};
Expr::Op(Op::Call, vec![head, args])
}
Ps::Op(op @ (Op::Tuple | Op::Dot | Op::LogAnd | Op::LogOr)) => Expr::Op(
op,
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::Fn => (BindOp::Fn, PPrec::Fn, 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),
// no consume!
_ => return Ok((BindOp::Match, PPrec::Alt, Some(TKind::FatArrow), Some(Prec::Body), None)),
@@ -481,11 +486,14 @@ impl<'t> Parse<'t> for Bind {
return Ok(Self(level, generics, pat, vec![]));
};
// `=>` for match, `=`? for everything else
if let Some(arrow) = arrow
&& p.next_if(arrow).allow_eof()?.is_none_or(|v| v.is_err())
{
return Ok(Self(level, generics, pat, vec![]));
// `=>` for match, `=` for `let`, `type`
if let Some(arrow) = arrow {
if p.next_if(arrow).allow_eof()?.is_none_or(|v| v.is_err()) {
return Ok(Self(level, generics, pat, vec![]));
}
} else {
// Allow prefix `=`? for the rest of them
p.next_if(TKind::Eq).allow_eof()?;
}
// `=` Expr

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::{
ast::*,
token::{TKind, Token},
@@ -8,9 +8,10 @@ use crate::{
///
/// Lower (toward [Prec::Min]) precedence levels can contain
/// 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 {
/// The lowest precedence
#[default]
Min,
/// "Alternate" pattern: `Pat | Pat`
Alt,
@@ -18,7 +19,7 @@ pub enum Prec {
Tuple,
/// Type annotation: `Pat : Pat`
Typed,
/// Function pattern: `Pat -> Pat`
/// Function signature: `foo(bar: baz, ..) -> qux`
Fn,
/// Range pattern: `Pat .. Pat`, `Pat ..= Pat`
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)
/// and its [precedence level](Prec)
/// and its following [precedence level](Prec)
fn from_infix(token: &Token) -> Option<(PatOp, Prec)> {
Some(match token.kind {
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::DotDot => (PatOp::RangeEx, 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?,
})
}
@@ -69,99 +118,70 @@ impl<'t> Parse<'t> for Pat {
type Prec = Prec;
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 tok.kind {
TKind::True | TKind::False | TKind::Character | TKind::Integer | TKind::String => {
Pat::Lit(p.parse(())?)
}
TKind::Bar => p.consume().parse(level)?,
TKind::Bang => p.consume().then(Pat::Never),
TKind::Fn => Pat::Op(PatOp::Fn, vec![p.consume().parse(Prec::Typed)?]),
TKind::Amp => Pat::Op(PatOp::Ref, vec![p.consume().parse(Prec::Max)?]),
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.
match p.peek().map(Token::kind) {
Ok(TKind::LParen) => Pat::NamedTuple(path, p.parse(Prec::Typed)?),
Ok(TKind::LCurly) if level <= Prec::Tuple.next() => Pat::NamedRecord(
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)?,
}
let mut head = match op {
Prefix::Consume => p.consume().parse(level)?,
Prefix::Underscore => p.consume().then(Pat::Ignore),
Prefix::Never => p.consume().then(Pat::Never),
Prefix::MetId => Pat::MetId(p.consume().next()?.lexeme.to_string()),
Prefix::Constant => Pat::Value(p.parse(ExPrec::Unary.value())?),
Prefix::Array => parse_array_pat(p)?,
Prefix::Id => {
let Anno(mut path, span): Anno<Path> = p.parse(())?;
// TODO: make these postfix.
match path.parts.len() {
1 => Pat::Name(path.parts.pop().expect("name has 1 part")),
_ => Pat::Value(Box::new(Anno(Expr::Id(path), span))),
}
},
TKind::Grave => Pat::MetId(p.consume().next()?.lexeme.to_string()),
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,
}
Prefix::Op(op @ (PatOp::Record | PatOp::Tuple)) => Pat::Op(
op,
p.consume()
.list(vec![], Prec::Typed, TKind::Comma, TKind::RCurly)?,
.list(vec![], Prec::Tuple.next(), TKind::Comma, kind.flip())?,
),
TKind::LParen => Pat::Op(
PatOp::Tuple,
p.consume()
.list(vec![], Prec::Typed, TKind::Comma, TKind::RParen)?,
),
TKind::LBrack => parse_array_pat(p)?,
_ => Err(ParseError::NotPattern(tok.kind, tok.span))?,
Prefix::Op(op @ (PatOp::Rest | PatOp::RangeEx | PatOp::RangeIn)) => {
// next token must continue a pattern
match p.consume().peek().allow_eof()? {
Some(tok) if from_prefix(tok).is_ok() => Pat::Op(op, vec![p.parse(prec)?]),
_ => Pat::Op(op, vec![]),
}
}
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)
&& level <= prec
{
let kind = tok.kind;
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(
op,
match p.consume().peek().map(Token::kind) {
Ok(TKind::Integer | TKind::Character | TKind::Identifier) => {
vec![head, p.parse(prec.next())?]
}
_ => vec![head],
if let Some(tok) = p.consume().peek().allow_eof()?
&& from_prefix(tok).is_ok()
{
vec![head, p.parse(prec)?]
} 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)

View File

@@ -66,8 +66,12 @@ impl std::fmt::Display for Lexeme {
/// The lexical classification of a [Token].
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TKind {
Comment, // Line or block comment
Doc, // Doc comment
/// Line or block comment
Comment,
/// Outer doc comment ///.*
OutDoc,
/// Inner doc comment: //!.*
InDoc,
As,
Break,
@@ -100,59 +104,165 @@ pub enum TKind {
Identifier, // or Keyword
Character,
String,
Integer, // 0(x[0-9A-Fa-f]* | d[0-9]* | o[0-7]* | b[0-1]*) | [1-9][0-9]*
LCurly, // {
RCurly, // }
LBrack, // [
RBrack, // ]
LParen, // (
RParen, // )
Amp, // &
AmpAmp, // &&
AmpEq, // &=
Arrow, // ->
At, // @
Backslash, // \
Bang, // !
BangBang, // !!
BangEq, // !=
Bar, // |
BarBar, // ||
BarEq, // |=
Colon, // :
ColonColon, // ::
Comma, // ,
Dot, // .
DotDot, // ..
DotDotEq, // ..=
Eq, // =
EqEq, // ==
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, // ^^
/// `0(x[0-9A-Fa-f]* | d[0-9]* | o[0-7]* | b[0-1]*) | [1-9][0-9]*`
Integer,
/// {
LCurly,
/// }
RCurly,
/// [
LBrack,
/// ]
RBrack,
/// (
LParen,
/// )
RParen,
/// &
Amp,
/// &&
AmpAmp,
/// &=
AmpEq,
/// ->
Arrow,
/// @
At,
/// \
Backslash,
/// !
Bang,
/// !!
BangBang,
/// !=
BangEq,
/// |
Bar,
/// ||
BarBar,
/// |=
BarEq,
/// :
Colon,
/// ::
ColonColon,
/// ,
Comma,
/// .
Dot,
/// ..
DotDot,
/// ..=
DotDotEq,
/// =
Eq,
/// ==
EqEq,
/// =>
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),
})
}
}