From 6e94b702c91f7cef16f2c4d90a1638b353e1efe6 Mon Sep 17 00:00:00 2001 From: John Date: Wed, 29 Jan 2025 04:05:11 -0600 Subject: [PATCH] cl-ast: Add pattern and match nodes, and associated behaviors --- compiler/cl-ast/src/ast.rs | 21 +++++ compiler/cl-ast/src/ast_impl.rs | 101 +++++++++++++++++++++++ compiler/cl-ast/src/ast_visitor/fold.rs | 51 ++++++++++-- compiler/cl-ast/src/ast_visitor/visit.rs | 50 +++++++++-- compiler/cl-repl/examples/yaml.rs | 59 ++++++++++--- 5 files changed, 261 insertions(+), 21 deletions(-) diff --git a/compiler/cl-ast/src/ast.rs b/compiler/cl-ast/src/ast.rs index 251c039..f96af4d 100644 --- a/compiler/cl-ast/src/ast.rs +++ b/compiler/cl-ast/src/ast.rs @@ -413,6 +413,27 @@ pub struct Let { pub init: Option>, } +/// A [Pattern] meta-expression (any [`ExprKind`] that fits pattern rules) +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum Pattern { + Path(Path), + Literal(Literal), + Ref(Mutability, Box), + Tuple(Vec), + Array(Vec), + Struct(Path, Vec<(Path, Option)>), +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Match { + pub scrutinee: Box, + pub arms: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct MatchArm(pub Pattern, pub Expr); + + /// An [Assign]ment expression: [`Expr`] ([`ModifyKind`] [`Expr`])\+ #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Assign { diff --git a/compiler/cl-ast/src/ast_impl.rs b/compiler/cl-ast/src/ast_impl.rs index f746ea8..bbb0444 100644 --- a/compiler/cl-ast/src/ast_impl.rs +++ b/compiler/cl-ast/src/ast_impl.rs @@ -464,6 +464,47 @@ mod display { } } + impl Display for Pattern { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Pattern::Path(path) => path.fmt(f), + Pattern::Literal(literal) => literal.fmt(f), + Pattern::Ref(mutability, pattern) => write!(f, "&{mutability}{pattern}"), + Pattern::Tuple(patterns) => separate(patterns, ", ")(f.delimit(INLINE_PARENS)), + Pattern::Array(patterns) => separate(patterns, ", ")(f.delimit(INLINE_SQUARE)), + Pattern::Struct(path, items) => { + write!(f, "{path}: ")?; + let f = &mut f.delimit(BRACES); + for (idx, (name, item)) in items.iter().enumerate() { + if idx != 0 { + f.write_str(",\n")?; + } + write!(f, "{name}")?; + if let Some(pattern) = item { + write!(f, ": {pattern}")? + } + } + Ok(()) + } + } + } + } + + impl Display for Match { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { scrutinee, arms } = self; + write!(f, "match {scrutinee} ")?; + separate(arms, ",\n")(f.delimit(BRACES)) + } + } + + impl Display for MatchArm { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self(pat, expr) = self; + write!(f, "{pat} => {expr}") + } + } + impl Display for Assign { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let Self { parts } = self; @@ -812,6 +853,50 @@ mod convert { Self { body: Some(value.into()) } } } + + impl TryFrom for Pattern { + type Error = ExprKind; + + /// Performs the conversion. On failure, returns the *first* non-pattern subexpression. + fn try_from(value: ExprKind) -> Result { + Ok(match value { + ExprKind::Literal(literal) => Pattern::Literal(literal), + ExprKind::Path(path) => Pattern::Path(path), + ExprKind::Empty => Pattern::Tuple(vec![]), + ExprKind::Group(Group { expr }) => Pattern::Tuple(vec![Pattern::try_from(*expr)?]), + ExprKind::Tuple(Tuple { exprs }) => Pattern::Tuple( + exprs + .into_iter() + .map(|e| Pattern::try_from(e.kind)) + .collect::>()?, + ), + ExprKind::AddrOf(AddrOf { mutable, expr }) => { + Pattern::Ref(mutable, Box::new(Pattern::try_from(*expr)?)) + } + ExprKind::Array(Array { values }) => Pattern::Array( + values + .into_iter() + .map(|e| Pattern::try_from(e.kind)) + .collect::>()?, + ), + // ExprKind::Index(index) => todo!(), + // ExprKind::Member(member) => todo!(), + ExprKind::Structor(Structor { to, init }) => { + let fields = init + .into_iter() + .map(|Fielder { name, init }| { + Ok(( + name.into(), + init.map(|i| Pattern::try_from(i.kind)).transpose()?, + )) + }) + .collect::>()?; + Pattern::Struct(to, fields) + } + err => Err(err)?, + }) + } + } } mod path { @@ -837,10 +922,26 @@ mod path { self } } + + /// Checks whether this path refers to the sinkhole identifier, `_` + pub fn is_sinkhole(&self) -> bool { + if let [PathPart::Ident(id)] = self.parts.as_slice() { + if let "_" = Sym::to_ref(id) { + return true; + } + } + false + } } impl PathPart { pub fn from_sym(ident: Sym) -> Self { Self::Ident(ident) } } + + impl From for Path { + fn from(value: Sym) -> Self { + Self { parts: vec![PathPart::Ident(value)], absolute: false } + } + } } diff --git a/compiler/cl-ast/src/ast_visitor/fold.rs b/compiler/cl-ast/src/ast_visitor/fold.rs index 402b693..9b31d23 100644 --- a/compiler/cl-ast/src/ast_visitor/fold.rs +++ b/compiler/cl-ast/src/ast_visitor/fold.rs @@ -229,6 +229,13 @@ pub trait Fold { fn fold_semi(&mut self, s: Semi) -> Semi { s } + fn fold_expr(&mut self, e: Expr) -> Expr { + let Expr { extents, kind } = e; + Expr { extents: self.fold_span(extents), kind: self.fold_expr_kind(kind) } + } + fn fold_expr_kind(&mut self, kind: ExprKind) -> ExprKind { + or_fold_expr_kind(self, kind) + } fn fold_let(&mut self, l: Let) -> Let { let Let { mutable, name, ty, init } = l; Let { @@ -238,13 +245,47 @@ pub trait Fold { init: init.map(|e| Box::new(self.fold_expr(*e))), } } - fn fold_expr(&mut self, e: Expr) -> Expr { - let Expr { extents, kind } = e; - Expr { extents: self.fold_span(extents), kind: self.fold_expr_kind(kind) } + + fn fold_pattern(&mut self, p: Pattern) -> Pattern { + match p { + Pattern::Path(path) => Pattern::Path(self.fold_path(path)), + Pattern::Literal(literal) => Pattern::Literal(self.fold_literal(literal)), + Pattern::Ref(mutability, pattern) => Pattern::Ref( + self.fold_mutability(mutability), + Box::new(self.fold_pattern(*pattern)), + ), + Pattern::Tuple(patterns) => { + Pattern::Tuple(patterns.into_iter().map(|p| self.fold_pattern(p)).collect()) + } + Pattern::Array(patterns) => { + Pattern::Array(patterns.into_iter().map(|p| self.fold_pattern(p)).collect()) + } + Pattern::Struct(path, items) => Pattern::Struct( + self.fold_path(path), + items + .into_iter() + .map(|(name, bind)| (name, bind.map(|p| self.fold_pattern(p)))) + .collect(), + ), + } } - fn fold_expr_kind(&mut self, kind: ExprKind) -> ExprKind { - or_fold_expr_kind(self, kind) + + fn fold_match(&mut self, m: Match) -> Match { + let Match { scrutinee, arms } = m; + Match { + scrutinee: self.fold_expr(*scrutinee).into(), + arms: arms + .into_iter() + .map(|arm| self.fold_match_arm(arm)) + .collect(), + } } + + fn fold_match_arm(&mut self, a: MatchArm) -> MatchArm { + let MatchArm(pat, expr) = a; + MatchArm(self.fold_pattern(pat), self.fold_expr(expr)) + } + fn fold_assign(&mut self, a: Assign) -> Assign { let Assign { parts } = a; let (head, tail) = *parts; diff --git a/compiler/cl-ast/src/ast_visitor/visit.rs b/compiler/cl-ast/src/ast_visitor/visit.rs index 4411ad2..acbafbd 100644 --- a/compiler/cl-ast/src/ast_visitor/visit.rs +++ b/compiler/cl-ast/src/ast_visitor/visit.rs @@ -192,6 +192,14 @@ pub trait Visit<'a>: Sized { or_visit_stmt_kind(self, kind) } fn visit_semi(&mut self, _s: &'a Semi) {} + fn visit_expr(&mut self, e: &'a Expr) { + let Expr { extents, kind } = e; + self.visit_span(extents); + self.visit_expr_kind(kind) + } + fn visit_expr_kind(&mut self, e: &'a ExprKind) { + or_visit_expr_kind(self, e) + } fn visit_let(&mut self, l: &'a Let) { let Let { mutable, name, ty, init } = l; self.visit_mutability(mutable); @@ -203,14 +211,44 @@ pub trait Visit<'a>: Sized { self.visit_expr(init) } } - fn visit_expr(&mut self, e: &'a Expr) { - let Expr { extents, kind } = e; - self.visit_span(extents); - self.visit_expr_kind(kind) + + fn visit_pattern(&mut self, p: &'a Pattern) { + match p { + Pattern::Path(path) => self.visit_path(path), + Pattern::Literal(literal) => self.visit_literal(literal), + Pattern::Ref(mutability, pattern) => { + self.visit_mutability(mutability); + self.visit_pattern(pattern); + } + Pattern::Tuple(patterns) => { + patterns.iter().for_each(|p| self.visit_pattern(p)); + } + Pattern::Array(patterns) => { + patterns.iter().for_each(|p| self.visit_pattern(p)); + } + Pattern::Struct(path, items) => { + self.visit_path(path); + items.iter().for_each(|(_name, bind)| { + bind.as_ref().inspect(|bind| { + self.visit_pattern(bind); + }); + }); + } + } } - fn visit_expr_kind(&mut self, e: &'a ExprKind) { - or_visit_expr_kind(self, e) + + fn visit_match(&mut self, m: &'a Match) { + let Match { scrutinee, arms } = m; + self.visit_expr(scrutinee); + arms.iter().for_each(|arm| self.visit_match_arm(arm)); } + + fn visit_match_arm(&mut self, a: &'a MatchArm) { + let MatchArm(pat, expr) = a; + self.visit_pattern(pat); + self.visit_expr(expr); + } + fn visit_assign(&mut self, a: &'a Assign) { let Assign { parts } = a; let (head, tail) = parts.as_ref(); diff --git a/compiler/cl-repl/examples/yaml.rs b/compiler/cl-repl/examples/yaml.rs index ca174f0..86fa1c9 100644 --- a/compiler/cl-repl/examples/yaml.rs +++ b/compiler/cl-repl/examples/yaml.rs @@ -369,16 +369,6 @@ pub mod yamlify { }; } } - impl Yamlify for Let { - fn yaml(&self, y: &mut Yamler) { - let Self { mutable, name, ty, init } = self; - y.key("Let") - .pair("name", name) - .yaml(mutable) - .pair("ty", ty) - .pair("init", init); - } - } impl Yamlify for Expr { fn yaml(&self, y: &mut Yamler) { let Self { extents: _, kind } = self; @@ -423,6 +413,55 @@ pub mod yamlify { y.key("Quote").value(self); } } + impl Yamlify for Let { + fn yaml(&self, y: &mut Yamler) { + let Self { mutable, name, ty, init } = self; + y.key("Let") + .pair("name", name) + .yaml(mutable) + .pair("ty", ty) + .pair("init", init); + } + } + + impl Yamlify for Pattern { + fn yaml(&self, y: &mut Yamler) { + match self { + Pattern::Path(path) => y.value(path), + Pattern::Literal(literal) => y.value(literal), + Pattern::Ref(mutability, pattern) => { + y.pair("mutability", mutability).pair("subpattern", pattern) + } + Pattern::Tuple(patterns) => y.key("Tuple").yaml(patterns), + Pattern::Array(patterns) => y.key("Array").yaml(patterns), + Pattern::Struct(path, items) => { + { + let mut y = y.key("Struct"); + y.pair("name", path); + for (name, item) in items { + y.pair(name, item); + } + } + y + } + }; + } + } + impl Yamlify for Match { + fn yaml(&self, y: &mut Yamler) { + let Self { scrutinee, arms } = self; + y.key("Match") + .pair("scrutinee", scrutinee) + .pair("arms", arms); + } + } + + impl Yamlify for MatchArm { + fn yaml(&self, y: &mut Yamler) { + let Self(pat, expr) = self; + y.pair("pat", pat).pair("expr", expr); + } + } impl Yamlify for Assign { fn yaml(&self, y: &mut Yamler) { let Self { parts } = self;