diff options
Diffstat (limited to 'src/syntax')
| -rw-r--r-- | src/syntax/expr.rs | 772 | ||||
| -rw-r--r-- | src/syntax/ident.rs | 12 | ||||
| -rw-r--r-- | src/syntax/markup.rs | 176 | ||||
| -rw-r--r-- | src/syntax/mod.rs | 688 | ||||
| -rw-r--r-- | src/syntax/pretty.rs | 143 | ||||
| -rw-r--r-- | src/syntax/span.rs | 15 | ||||
| -rw-r--r-- | src/syntax/token.rs | 271 | ||||
| -rw-r--r-- | src/syntax/visit.rs | 263 |
8 files changed, 1492 insertions, 848 deletions
diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index 904515ba..d0d0c62f 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -1,75 +1,50 @@ -use std::rc::Rc; - -use super::{Ident, Markup, Span, Token}; +use super::{Ident, Markup, NodeKind, RedNode, RedTicket, Span, TypedNode}; use crate::geom::{AngularUnit, LengthUnit}; +use crate::node; use crate::util::EcoString; /// An expression. #[derive(Debug, Clone, PartialEq)] pub enum Expr { /// An identifier: `left`. - Ident(Box<Ident>), + Ident(Ident), /// A literal: `1`, `true`, ... - Lit(Box<Lit>), + Lit(Lit), /// An array expression: `(1, "hi", 12cm)`. - Array(Box<ArrayExpr>), + Array(ArrayExpr), /// A dictionary expression: `(thickness: 3pt, pattern: dashed)`. - Dict(Box<DictExpr>), + Dict(DictExpr), /// A template expression: `[*Hi* there!]`. - Template(Box<TemplateExpr>), + Template(TemplateExpr), /// A grouped expression: `(1 + 2)`. - Group(Box<GroupExpr>), + Group(GroupExpr), /// A block expression: `{ let x = 1; x + 2 }`. - Block(Box<BlockExpr>), + Block(BlockExpr), /// A unary operation: `-x`. - Unary(Box<UnaryExpr>), + Unary(UnaryExpr), /// A binary operation: `a + b`. - Binary(Box<BinaryExpr>), + Binary(BinaryExpr), /// An invocation of a function: `f(x, y)`. - Call(Box<CallExpr>), + Call(CallExpr), /// A closure expression: `(x, y) => z`. - Closure(Box<ClosureExpr>), + Closure(ClosureExpr), /// A with expression: `f with (x, y: 1)`. - With(Box<WithExpr>), + With(WithExpr), /// A let expression: `let x = 1`. - Let(Box<LetExpr>), + Let(LetExpr), /// An if-else expression: `if x { y } else { z }`. - If(Box<IfExpr>), + If(IfExpr), /// A while loop expression: `while x { y }`. - While(Box<WhileExpr>), + While(WhileExpr), /// A for loop expression: `for x in y { z }`. - For(Box<ForExpr>), + For(ForExpr), /// An import expression: `import a, b, c from "utils.typ"`. - Import(Box<ImportExpr>), + Import(ImportExpr), /// An include expression: `include "chapter1.typ"`. - Include(Box<IncludeExpr>), + Include(IncludeExpr), } impl Expr { - /// The source code location. - pub fn span(&self) -> Span { - match self { - Self::Ident(v) => v.span, - Self::Lit(v) => v.span(), - Self::Array(v) => v.span, - Self::Dict(v) => v.span, - Self::Template(v) => v.span, - Self::Group(v) => v.span, - Self::Block(v) => v.span, - Self::Unary(v) => v.span, - Self::Binary(v) => v.span, - Self::Call(v) => v.span, - Self::Closure(v) => v.span, - Self::With(v) => v.span, - Self::Let(v) => v.span, - Self::If(v) => v.span, - Self::While(v) => v.span, - Self::For(v) => v.span, - Self::Import(v) => v.span, - Self::Include(v) => v.span, - } - } - /// Whether the expression can be shortened in markup with a hashtag. pub fn has_short_form(&self) -> bool { matches!(self, @@ -83,6 +58,63 @@ impl Expr { | Self::Include(_) ) } + + /// Return the expression's span. + pub fn span(&self) -> Span { + match self { + Self::Ident(ident) => ident.span, + Self::Lit(lit) => lit.span(), + Self::Array(array) => array.span(), + Self::Dict(dict) => dict.span(), + Self::Template(template) => template.span(), + Self::Group(group) => group.span(), + Self::Block(block) => block.span(), + Self::Unary(unary) => unary.span(), + Self::Binary(binary) => binary.span(), + Self::Call(call) => call.span(), + Self::Closure(closure) => closure.span(), + Self::With(with) => with.span(), + Self::Let(let_) => let_.span(), + Self::If(if_) => if_.span(), + Self::While(while_) => while_.span(), + Self::For(for_) => for_.span(), + Self::Import(import) => import.span(), + Self::Include(include) => include.span(), + } + } +} + +impl TypedNode for Expr { + fn cast_from(node: RedTicket) -> Option<Self> { + match node.kind() { + NodeKind::Ident(_) => Some(Self::Ident(Ident::cast_from(node).unwrap())), + NodeKind::Array => Some(Self::Array(ArrayExpr::cast_from(node).unwrap())), + NodeKind::Dict => Some(Self::Dict(DictExpr::cast_from(node).unwrap())), + NodeKind::Template => { + Some(Self::Template(TemplateExpr::cast_from(node).unwrap())) + } + NodeKind::Group => Some(Self::Group(GroupExpr::cast_from(node).unwrap())), + NodeKind::Block => Some(Self::Block(BlockExpr::cast_from(node).unwrap())), + NodeKind::Unary => Some(Self::Unary(UnaryExpr::cast_from(node).unwrap())), + NodeKind::Binary => Some(Self::Binary(BinaryExpr::cast_from(node).unwrap())), + NodeKind::Call => Some(Self::Call(CallExpr::cast_from(node).unwrap())), + NodeKind::Closure => { + Some(Self::Closure(ClosureExpr::cast_from(node).unwrap())) + } + NodeKind::WithExpr => Some(Self::With(WithExpr::cast_from(node).unwrap())), + NodeKind::LetExpr => Some(Self::Let(LetExpr::cast_from(node).unwrap())), + NodeKind::IfExpr => Some(Self::If(IfExpr::cast_from(node).unwrap())), + NodeKind::WhileExpr => Some(Self::While(WhileExpr::cast_from(node).unwrap())), + NodeKind::ForExpr => Some(Self::For(ForExpr::cast_from(node).unwrap())), + NodeKind::ImportExpr => { + Some(Self::Import(ImportExpr::cast_from(node).unwrap())) + } + NodeKind::IncludeExpr => { + Some(Self::Include(IncludeExpr::cast_from(node).unwrap())) + } + _ => Some(Self::Lit(Lit::cast_from(node)?)), + } + } } /// A literal: `1`, `true`, ... @@ -113,94 +145,145 @@ pub enum Lit { Str(Span, EcoString), } +impl TypedNode for Lit { + fn cast_from(node: RedTicket) -> Option<Self> { + match node.kind() { + NodeKind::None => Some(Self::None(node.own().span())), + NodeKind::Auto => Some(Self::Auto(node.own().span())), + NodeKind::Bool(b) => Some(Self::Bool(node.own().span(), *b)), + NodeKind::Int(i) => Some(Self::Int(node.own().span(), *i)), + NodeKind::Float(f) => Some(Self::Float(node.own().span(), *f)), + NodeKind::Length(f, unit) => Some(Self::Length(node.own().span(), *f, *unit)), + NodeKind::Angle(f, unit) => Some(Self::Angle(node.own().span(), *f, *unit)), + NodeKind::Percentage(f) => Some(Self::Percent(node.own().span(), *f)), + NodeKind::Fraction(f) => Some(Self::Fractional(node.own().span(), *f)), + NodeKind::Str(s) => Some(Self::Str(node.own().span(), s.string.clone())), + _ => None, + } + } +} + impl Lit { - /// The source code location. pub fn span(&self) -> Span { - match *self { - Self::None(span) => span, - Self::Auto(span) => span, - Self::Bool(span, _) => span, - Self::Int(span, _) => span, - Self::Float(span, _) => span, - Self::Length(span, _, _) => span, - Self::Angle(span, _, _) => span, - Self::Percent(span, _) => span, - Self::Fractional(span, _) => span, - Self::Str(span, _) => span, + match self { + Self::None(span) => *span, + Self::Auto(span) => *span, + Self::Bool(span, _) => *span, + Self::Int(span, _) => *span, + Self::Float(span, _) => *span, + Self::Length(span, _, _) => *span, + Self::Angle(span, _, _) => *span, + Self::Percent(span, _) => *span, + Self::Fractional(span, _) => *span, + Self::Str(span, _) => *span, } } } -/// An array expression: `(1, "hi", 12cm)`. -#[derive(Debug, Clone, PartialEq)] -pub struct ArrayExpr { - /// The source code location. - pub span: Span, - /// The entries of the array. - pub items: Vec<Expr>, -} +node!( + /// An array expression: `(1, "hi", 12cm)`. + Array => ArrayExpr +); -/// A dictionary expression: `(thickness: 3pt, pattern: dashed)`. -#[derive(Debug, Clone, PartialEq)] -pub struct DictExpr { - /// The source code location. - pub span: Span, - /// The named dictionary entries. - pub items: Vec<Named>, +impl ArrayExpr { + /// The array items. + pub fn items(&self) -> Vec<Expr> { + self.0.children().filter_map(RedTicket::cast).collect() + } } -/// A pair of a name and an expression: `pattern: dashed`. -#[derive(Debug, Clone, PartialEq)] -pub struct Named { - /// The name: `pattern`. - pub name: Ident, - /// The right-hand side of the pair: `dashed`. - pub expr: Expr, +node!( + /// A dictionary expression: `(thickness: 3pt, pattern: dashed)`. + Dict => DictExpr +); + +impl DictExpr { + /// The named dictionary items. + pub fn items(&self) -> Vec<Named> { + self.0.children().filter_map(RedTicket::cast).collect() + } } +node!( + /// A pair of a name and an expression: `pattern: dashed`. + Named +); + impl Named { - /// The source code location. - pub fn span(&self) -> Span { - self.name.span.join(self.expr.span()) + /// The name: `pattern`. + pub fn name(&self) -> Ident { + self.0.cast_first_child().expect("named pair is missing name ident") + } + + /// The right-hand side of the pair: `dashed`. + pub fn expr(&self) -> Expr { + self.0 + .children() + .filter_map(RedTicket::cast) + .nth(1) + .expect("named pair is missing expression") } } -/// A template expression: `[*Hi* there!]`. -#[derive(Debug, Clone, PartialEq)] -pub struct TemplateExpr { - /// The source code location. - pub span: Span, +node!( + /// A template expression: `[*Hi* there!]`. + Template => TemplateExpr +); + +impl TemplateExpr { /// The contents of the template. - pub body: Markup, + pub fn body(&self) -> Markup { + self.0 + .cast_first_child() + .expect("template expression is missing body") + } } -/// A grouped expression: `(1 + 2)`. -#[derive(Debug, Clone, PartialEq)] -pub struct GroupExpr { - /// The source code location. - pub span: Span, +node!( + /// A grouped expression: `(1 + 2)`. + Group => GroupExpr +); + +impl GroupExpr { /// The wrapped expression. - pub expr: Expr, + pub fn expr(&self) -> Expr { + self.0 + .cast_first_child() + .expect("group expression is missing expression") + } } -/// A block expression: `{ let x = 1; x + 2 }`. -#[derive(Debug, Clone, PartialEq)] -pub struct BlockExpr { - /// The source code location. - pub span: Span, +node!( + /// A block expression: `{ let x = 1; x + 2 }`. + Block => BlockExpr +); + +impl BlockExpr { /// The list of expressions contained in the block. - pub exprs: Vec<Expr>, + pub fn exprs(&self) -> Vec<Expr> { + self.0.children().filter_map(RedTicket::cast).collect() + } } -/// A unary operation: `-x`. -#[derive(Debug, Clone, PartialEq)] -pub struct UnaryExpr { - /// The source code location. - pub span: Span, +node!( + /// A unary operation: `-x`. + Unary => UnaryExpr +); + +impl UnaryExpr { /// The operator: `-`. - pub op: UnOp, + pub fn op(&self) -> UnOp { + self.0 + .cast_first_child() + .expect("unary expression is missing operator") + } + /// The expression to operator on: `x`. - pub expr: Expr, + pub fn expr(&self) -> Expr { + self.0 + .cast_first_child() + .expect("unary expression is missing expression") + } } /// A unary operator. @@ -214,13 +297,19 @@ pub enum UnOp { Not, } +impl TypedNode for UnOp { + fn cast_from(node: RedTicket) -> Option<Self> { + Self::from_token(node.kind()) + } +} + impl UnOp { /// Try to convert the token into a unary operation. - pub fn from_token(token: Token) -> Option<Self> { + pub fn from_token(token: &NodeKind) -> Option<Self> { Some(match token { - Token::Plus => Self::Pos, - Token::Hyph => Self::Neg, - Token::Not => Self::Not, + NodeKind::Plus => Self::Pos, + NodeKind::Minus => Self::Neg, + NodeKind::Not => Self::Not, _ => return None, }) } @@ -229,7 +318,7 @@ impl UnOp { pub fn precedence(self) -> usize { match self { Self::Pos | Self::Neg => 8, - Self::Not => 3, + Self::Not => 4, } } @@ -243,17 +332,34 @@ impl UnOp { } } -/// A binary operation: `a + b`. -#[derive(Debug, Clone, PartialEq)] -pub struct BinaryExpr { - /// The source code location. - pub span: Span, +node!( + /// A binary operation: `a + b`. + Binary => BinaryExpr +); + +impl BinaryExpr { + /// The binary operator: `+`. + pub fn op(&self) -> BinOp { + self.0 + .cast_first_child() + .expect("binary expression is missing operator") + } + /// The left-hand side of the operation: `a`. - pub lhs: Expr, - /// The operator: `+`. - pub op: BinOp, + pub fn lhs(&self) -> Expr { + self.0 + .cast_first_child() + .expect("binary expression is missing left-hand side") + } + /// The right-hand side of the operation: `b`. - pub rhs: Expr, + pub fn rhs(&self) -> Expr { + self.0 + .children() + .filter_map(RedTicket::cast) + .nth(1) + .expect("binary expression is missing right-hand side") + } } /// A binary operator. @@ -295,27 +401,33 @@ pub enum BinOp { DivAssign, } +impl TypedNode for BinOp { + fn cast_from(node: RedTicket) -> Option<Self> { + Self::from_token(node.kind()) + } +} + impl BinOp { /// Try to convert the token into a binary operation. - pub fn from_token(token: Token) -> Option<Self> { + pub fn from_token(token: &NodeKind) -> Option<Self> { Some(match token { - Token::Plus => Self::Add, - Token::Hyph => Self::Sub, - Token::Star => Self::Mul, - Token::Slash => Self::Div, - Token::And => Self::And, - Token::Or => Self::Or, - Token::EqEq => Self::Eq, - Token::ExclEq => Self::Neq, - Token::Lt => Self::Lt, - Token::LtEq => Self::Leq, - Token::Gt => Self::Gt, - Token::GtEq => Self::Geq, - Token::Eq => Self::Assign, - Token::PlusEq => Self::AddAssign, - Token::HyphEq => Self::SubAssign, - Token::StarEq => Self::MulAssign, - Token::SlashEq => Self::DivAssign, + NodeKind::Plus => Self::Add, + NodeKind::Minus => Self::Sub, + NodeKind::Star => Self::Mul, + NodeKind::Slash => Self::Div, + NodeKind::And => Self::And, + NodeKind::Or => Self::Or, + NodeKind::EqEq => Self::Eq, + NodeKind::ExclEq => Self::Neq, + NodeKind::Lt => Self::Lt, + NodeKind::LtEq => Self::Leq, + NodeKind::Gt => Self::Gt, + NodeKind::GtEq => Self::Geq, + NodeKind::Eq => Self::Assign, + NodeKind::PlusEq => Self::AddAssign, + NodeKind::HyphEq => Self::SubAssign, + NodeKind::StarEq => Self::MulAssign, + NodeKind::SlashEq => Self::DivAssign, _ => return None, }) } @@ -392,27 +504,35 @@ pub enum Associativity { Right, } -/// An invocation of a function: `foo(...)`. -#[derive(Debug, Clone, PartialEq)] -pub struct CallExpr { - /// The source code location. - pub span: Span, +node!( + /// An invocation of a function: `foo(...)`. + Call => CallExpr +); + +impl CallExpr { /// The function to call. - pub callee: Expr, + pub fn callee(&self) -> Expr { + self.0.cast_first_child().expect("call expression is missing callee") + } + /// The arguments to the function. - pub args: CallArgs, + pub fn args(&self) -> CallArgs { + self.0 + .cast_first_child() + .expect("call expression is missing argument list") + } } -/// The arguments to a function: `12, draw: false`. -/// -/// In case of a bracketed invocation with a body, the body is _not_ -/// included in the span for the sake of clearer error messages. -#[derive(Debug, Clone, PartialEq)] -pub struct CallArgs { - /// The source code location. - pub span: Span, +node!( + /// The arguments to a function: `12, draw: false`. + CallArgs +); + +impl CallArgs { /// The positional and named arguments. - pub items: Vec<CallArg>, + pub fn items(&self) -> Vec<CallArg> { + self.0.children().filter_map(RedTicket::cast).collect() + } } /// An argument to a function call. @@ -426,30 +546,75 @@ pub enum CallArg { Spread(Expr), } +impl TypedNode for CallArg { + fn cast_from(node: RedTicket) -> Option<Self> { + match node.kind() { + NodeKind::Named => Some(CallArg::Named( + node.cast().expect("named call argument is missing name"), + )), + NodeKind::ParameterSink => Some(CallArg::Spread( + node.own() + .cast_first_child() + .expect("call argument sink is missing expression"), + )), + _ => Some(CallArg::Pos(node.cast()?)), + } + } +} + impl CallArg { - /// The source code location. + /// The name of this argument. pub fn span(&self) -> Span { match self { - Self::Pos(expr) => expr.span(), Self::Named(named) => named.span(), + Self::Pos(expr) => expr.span(), Self::Spread(expr) => expr.span(), } } } -/// A closure expression: `(x, y) => z`. -#[derive(Debug, Clone, PartialEq)] -pub struct ClosureExpr { - /// The source code location. - pub span: Span, +node!( + /// A closure expression: `(x, y) => z`. + Closure => ClosureExpr +); + +impl ClosureExpr { /// The name of the closure. /// /// This only exists if you use the function syntax sugar: `let f(x) = y`. - pub name: Option<Ident>, + pub fn name(&self) -> Option<Ident> { + // `first_convert_child` does not work here because of the Option in the + // Result. + self.0.cast_first_child() + } + /// The parameter bindings. - pub params: Vec<ClosureParam>, + pub fn params(&self) -> Vec<ClosureParam> { + self.0 + .children() + .find(|x| x.kind() == &NodeKind::ClosureParams) + .expect("closure is missing parameter list") + .own() + .children() + .filter_map(RedTicket::cast) + .collect() + } + /// The body of the closure. - pub body: Rc<Expr>, + pub fn body(&self) -> Expr { + // The filtering for the NodeKind is necessary here because otherwise, + // `first_convert_child` will use the Ident if present. + self.0.cast_last_child().expect("closure is missing body") + } + + /// The ticket of the body of the closure. + pub fn body_ticket(&self) -> RedTicket { + self.0 + .children() + .filter(|x| x.cast::<Expr>().is_some()) + .last() + .unwrap() + } } /// An parameter to a closure. @@ -463,50 +628,111 @@ pub enum ClosureParam { Sink(Ident), } -impl ClosureParam { - /// The source code location. - pub fn span(&self) -> Span { - match self { - Self::Pos(ident) => ident.span, - Self::Named(named) => named.span(), - Self::Sink(ident) => ident.span, +impl TypedNode for ClosureParam { + fn cast_from(node: RedTicket) -> Option<Self> { + match node.kind() { + NodeKind::Ident(i) => { + Some(ClosureParam::Pos(Ident::new(i, node.own().span()).unwrap())) + } + NodeKind::Named => Some(ClosureParam::Named( + node.cast().expect("named closure parameter is missing name"), + )), + NodeKind::ParameterSink => Some(ClosureParam::Sink( + node.own() + .cast_first_child() + .expect("closure parameter sink is missing identifier"), + )), + _ => Some(ClosureParam::Pos(node.cast()?)), } } } -/// A with expression: `f with (x, y: 1)`. -/// -/// Applies arguments to a function. -#[derive(Debug, Clone, PartialEq)] -pub struct WithExpr { - /// The source code location. - pub span: Span, +node!( + /// A with expression: `f with (x, y: 1)`. + WithExpr +); + +impl WithExpr { /// The function to apply the arguments to. - pub callee: Expr, + pub fn callee(&self) -> Expr { + self.0 + .cast_first_child() + .expect("with expression is missing callee expression") + } + /// The arguments to apply to the function. - pub args: CallArgs, + pub fn args(&self) -> CallArgs { + self.0 + .cast_first_child() + .expect("with expression is missing argument list") + } } -/// A let expression: `let x = 1`. -#[derive(Debug, Clone, PartialEq)] -pub struct LetExpr { - /// The source code location. - pub span: Span, +node!( + /// A let expression: `let x = 1`. + LetExpr +); + +impl LetExpr { /// The binding to assign to. - pub binding: Ident, + pub fn binding(&self) -> Ident { + if let Some(c) = self.0.cast_first_child() { + c + } else if let Some(w) = self.0.typed_child(&NodeKind::WithExpr) { + // Can't do an `first_convert_child` here because the WithExpr's + // callee has to be an identifier. + w.cast_first_child() + .expect("with expression is missing an identifier callee") + } else if let Some(Expr::Closure(c)) = self.0.cast_last_child() { + c.name().expect("closure is missing an identifier name") + } else { + panic!("let expression is missing either an identifier or a with expression") + } + } + /// The expression the binding is initialized with. - pub init: Option<Expr>, + pub fn init(&self) -> Option<Expr> { + if self.0.cast_first_child::<Ident>().is_some() { + self.0.children().filter_map(RedTicket::cast).nth(1) + } else { + Some( + self.0 + .cast_first_child() + .expect("let expression is missing a with expression"), + ) + } + } + + /// The ticket for the expression the binding is initialized with. + pub fn init_ticket(&self) -> RedTicket { + if self.0.cast_first_child::<Ident>().is_some() { + self.0.children().filter(|x| x.cast::<Expr>().is_some()).nth(1) + } else { + self.0.children().find(|x| x.cast::<Expr>().is_some()) + } + .unwrap() + } } -/// An import expression: `import a, b, c from "utils.typ"`. -#[derive(Debug, Clone, PartialEq)] -pub struct ImportExpr { - /// The source code location. - pub span: Span, +node!( + /// An import expression: `import a, b, c from "utils.typ"`. + ImportExpr +); + +impl ImportExpr { /// The items to be imported. - pub imports: Imports, + pub fn imports(&self) -> Imports { + self.0 + .cast_first_child() + .expect("import expression is missing import list") + } + /// The location of the importable file. - pub path: Expr, + pub fn path(&self) -> Expr { + self.0 + .cast_first_child() + .expect("import expression is missing path expression") + } } /// The items that ought to be imported from a file. @@ -518,67 +744,137 @@ pub enum Imports { Idents(Vec<Ident>), } -/// An include expression: `include "chapter1.typ"`. -#[derive(Debug, Clone, PartialEq)] -pub struct IncludeExpr { - /// The source code location. - pub span: Span, +impl TypedNode for Imports { + fn cast_from(node: RedTicket) -> Option<Self> { + match node.kind() { + NodeKind::Star => Some(Imports::Wildcard), + NodeKind::ImportItems => { + let idents = node.own().children().filter_map(RedTicket::cast).collect(); + Some(Imports::Idents(idents)) + } + _ => None, + } + } +} + +node!( + /// An include expression: `include "chapter1.typ"`. + IncludeExpr +); + +impl IncludeExpr { /// The location of the file to be included. - pub path: Expr, + pub fn path(&self) -> Expr { + self.0 + .cast_first_child() + .expect("include expression is missing path expression") + } } -/// An if-else expression: `if x { y } else { z }`. -#[derive(Debug, Clone, PartialEq)] -pub struct IfExpr { - /// The source code location. - pub span: Span, +node!( + /// An if-else expression: `if x { y } else { z }`. + IfExpr +); + +impl IfExpr { /// The condition which selects the body to evaluate. - pub condition: Expr, + pub fn condition(&self) -> Expr { + self.0 + .cast_first_child() + .expect("if expression is missing condition expression") + } + /// The expression to evaluate if the condition is true. - pub if_body: Expr, + pub fn if_body(&self) -> Expr { + self.0 + .children() + .filter_map(RedTicket::cast) + .nth(1) + .expect("if expression is missing if body") + } + /// The expression to evaluate if the condition is false. - pub else_body: Option<Expr>, + pub fn else_body(&self) -> Option<Expr> { + self.0.children().filter_map(RedTicket::cast).nth(2) + } } -/// A while loop expression: `while x { y }`. -#[derive(Debug, Clone, PartialEq)] -pub struct WhileExpr { - /// The source code location. - pub span: Span, +node!( + /// A while loop expression: `while x { y }`. + WhileExpr +); + +impl WhileExpr { /// The condition which selects whether to evaluate the body. - pub condition: Expr, + pub fn condition(&self) -> Expr { + self.0 + .cast_first_child() + .expect("while loop expression is missing condition expression") + } + /// The expression to evaluate while the condition is true. - pub body: Expr, + pub fn body(&self) -> Expr { + self.0 + .children() + .filter_map(RedTicket::cast) + .nth(1) + .expect("while loop expression is missing body") + } } -/// A for loop expression: `for x in y { z }`. -#[derive(Debug, Clone, PartialEq)] -pub struct ForExpr { - /// The source code location. - pub span: Span, +node!( + /// A for loop expression: `for x in y { z }`. + ForExpr +); + +impl ForExpr { /// The pattern to assign to. - pub pattern: ForPattern, + pub fn pattern(&self) -> ForPattern { + self.0 + .cast_first_child() + .expect("for loop expression is missing pattern") + } + /// The expression to iterate over. - pub iter: Expr, + pub fn iter(&self) -> Expr { + self.0 + .cast_first_child() + .expect("for loop expression is missing iterable expression") + } + /// The expression to evaluate for each iteration. - pub body: Expr, -} + pub fn body(&self) -> Expr { + self.0 + .children() + .filter_map(RedTicket::cast) + .last() + .expect("for loop expression is missing body") + } -/// A pattern in a for loop. -#[derive(Debug, Clone, PartialEq)] -pub enum ForPattern { - /// A value pattern: `for v in array`. - Value(Ident), - /// A key-value pattern: `for k, v in dict`. - KeyValue(Ident, Ident), + /// The ticket for the expression to evaluate for each iteration. + pub fn body_ticket(&self) -> RedTicket { + self.0 + .children() + .filter(|x| x.cast::<Expr>().is_some()) + .last() + .unwrap() + } } +node!( + /// A for-in loop expression: `for x in y { z }`. + ForPattern +); + impl ForPattern { - /// The source code location. - pub fn span(&self) -> Span { - match self { - Self::Value(v) => v.span, - Self::KeyValue(k, v) => k.span.join(v.span), - } + pub fn key(&self) -> Option<Ident> { + let mut items: Vec<_> = self.0.children().filter_map(RedTicket::cast).collect(); + if items.len() > 1 { Some(items.remove(0)) } else { None } + } + + pub fn value(&self) -> Ident { + self.0 + .cast_last_child() + .expect("for-in loop pattern is missing value") } } diff --git a/src/syntax/ident.rs b/src/syntax/ident.rs index 398e2ff9..2c61329d 100644 --- a/src/syntax/ident.rs +++ b/src/syntax/ident.rs @@ -3,7 +3,7 @@ use std::ops::Deref; use unicode_xid::UnicodeXID; -use super::Span; +use super::{NodeKind, RedTicket, Span, TypedNode}; use crate::util::EcoString; /// An unicode identifier with a few extra permissible characters. @@ -66,6 +66,16 @@ impl From<&Ident> for EcoString { } } +impl TypedNode for Ident { + fn cast_from(node: RedTicket) -> Option<Self> { + if let NodeKind::Ident(i) = node.kind() { + Some(Ident::new(i, node.own().span()).unwrap()) + } else { + None + } + } +} + /// Whether a string is a valid identifier. pub fn is_ident(string: &str) -> bool { let mut chars = string.chars(); diff --git a/src/syntax/markup.rs b/src/syntax/markup.rs index 09a37116..c12c0e81 100644 --- a/src/syntax/markup.rs +++ b/src/syntax/markup.rs @@ -1,41 +1,87 @@ -use super::{Expr, Ident, Span}; +use super::{Expr, Ident, NodeKind, RedNode, RedTicket, Span, TypedNode}; +use crate::node; use crate::util::EcoString; +use std::fmt::Write; /// The syntactical root capable of representing a full parsed document. pub type Markup = Vec<MarkupNode>; +impl TypedNode for Markup { + fn cast_from(node: RedTicket) -> Option<Self> { + if node.kind() != &NodeKind::Markup { + return None; + } + + let children = node.own().children().filter_map(TypedNode::cast_from).collect(); + Some(children) + } +} + /// A single piece of markup. #[derive(Debug, Clone, PartialEq)] pub enum MarkupNode { /// Whitespace containing less than two newlines. Space, /// A forced line break: `\`. - Linebreak(Span), + Linebreak, /// A paragraph break: Two or more newlines. - Parbreak(Span), + Parbreak, /// Strong text was enabled / disabled: `*`. - Strong(Span), + Strong, /// Emphasized text was enabled / disabled: `_`. - Emph(Span), + Emph, /// Plain text. Text(EcoString), /// A raw block with optional syntax highlighting: `` `...` ``. - Raw(Box<RawNode>), + Raw(RawNode), /// A section heading: `= Introduction`. - Heading(Box<HeadingNode>), + Heading(HeadingNode), /// An item in an unordered list: `- ...`. - List(Box<ListNode>), + List(ListNode), /// An item in an enumeration (ordered list): `1. ...`. - Enum(Box<EnumNode>), + Enum(EnumNode), /// An expression. Expr(Expr), } +impl TypedNode for MarkupNode { + fn cast_from(node: RedTicket) -> Option<Self> { + match node.kind() { + NodeKind::Space(_) => Some(MarkupNode::Space), + NodeKind::Linebreak => Some(MarkupNode::Linebreak), + NodeKind::Parbreak => Some(MarkupNode::Parbreak), + NodeKind::Strong => Some(MarkupNode::Strong), + NodeKind::Emph => Some(MarkupNode::Emph), + NodeKind::Text(s) => Some(MarkupNode::Text(s.clone())), + NodeKind::UnicodeEscape(u) => { + Some(MarkupNode::Text(if let Some(s) = u.character { + s.into() + } else { + let mut eco = EcoString::with_capacity(u.sequence.len() + 4); + write!(&mut eco, "\\u{{{}}}", u.sequence).unwrap(); + eco + })) + } + NodeKind::EnDash => Some(MarkupNode::Text(EcoString::from("\u{2013}"))), + NodeKind::EmDash => Some(MarkupNode::Text(EcoString::from("\u{2014}"))), + NodeKind::NonBreakingSpace => { + Some(MarkupNode::Text(EcoString::from("\u{00A0}"))) + } + NodeKind::Raw(_) => Some(MarkupNode::Raw(RawNode::cast_from(node).unwrap())), + NodeKind::Heading => { + Some(MarkupNode::Heading(HeadingNode::cast_from(node).unwrap())) + } + NodeKind::List => Some(MarkupNode::List(ListNode::cast_from(node).unwrap())), + NodeKind::Enum => Some(MarkupNode::Enum(EnumNode::cast_from(node).unwrap())), + NodeKind::Error(_, _) => None, + _ => Some(MarkupNode::Expr(Expr::cast_from(node)?)), + } + } +} + /// A raw block with optional syntax highlighting: `` `...` ``. #[derive(Debug, Clone, PartialEq)] pub struct RawNode { - /// The source code location. - pub span: Span, /// An optional identifier specifying the language to syntax-highlight in. pub lang: Option<Ident>, /// The raw text, determined as the raw string between the backticks trimmed @@ -46,33 +92,97 @@ pub struct RawNode { pub block: bool, } -/// A section heading: `= Introduction`. -#[derive(Debug, Clone, PartialEq)] -pub struct HeadingNode { - /// The source code location. - pub span: Span, - /// The section depth (numer of equals signs). - pub level: usize, +impl TypedNode for RawNode { + fn cast_from(node: RedTicket) -> Option<Self> { + if let NodeKind::Raw(raw) = node.kind() { + let span = node.own().span(); + let start = span.start + raw.backticks as usize; + Some(Self { + block: raw.block, + lang: raw.lang.as_ref().and_then(|x| { + let span = Span::new(span.source, start, start + x.len()); + Ident::new(x, span) + }), + text: raw.text.clone(), + }) + } else { + None + } + } +} + +node!( + /// A section heading: `= Introduction`. + Heading => HeadingNode +); + +impl HeadingNode { /// The contents of the heading. - pub body: Markup, + pub fn body(&self) -> Markup { + self.0 + .cast_first_child() + .expect("heading node is missing markup body") + } + + /// The section depth (numer of equals signs). + pub fn level(&self) -> HeadingLevel { + self.0 + .cast_first_child() + .expect("heading node is missing heading level") + } } -/// An item in an unordered list: `- ...`. -#[derive(Debug, Clone, PartialEq)] -pub struct ListNode { - /// The source code location. - pub span: Span, +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct HeadingLevel(pub usize); + +impl TypedNode for HeadingLevel { + fn cast_from(node: RedTicket) -> Option<Self> { + if let NodeKind::HeadingLevel(l) = node.kind() { + Some(Self((*l).into())) + } else { + None + } + } +} + +node!( + /// An item in an unordered list: `- ...`. + List => ListNode +); + +impl ListNode { /// The contents of the list item. - pub body: Markup, + pub fn body(&self) -> Markup { + self.0.cast_first_child().expect("list node is missing body") + } } -/// An item in an enumeration (ordered list): `1. ...`. -#[derive(Debug, Clone, PartialEq)] -pub struct EnumNode { - /// The source code location. - pub span: Span, - /// The number, if any. - pub number: Option<usize>, +node!( + /// An item in an enumeration (ordered list): `1. ...`. + Enum => EnumNode +); + +impl EnumNode { /// The contents of the list item. - pub body: Markup, + pub fn body(&self) -> Markup { + self.0.cast_first_child().expect("enumeration node is missing body") + } + + /// The number, if any. + pub fn number(&self) -> EnumNumber { + self.0.cast_first_child().expect("enumeration node is missing number") + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct EnumNumber(pub Option<usize>); + +impl TypedNode for EnumNumber { + fn cast_from(node: RedTicket) -> Option<Self> { + if let NodeKind::EnumNumbering(x) = node.kind() { + Some(Self(*x)) + } else { + None + } + } } diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index 8dbb108d..88757f8e 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -6,7 +6,11 @@ mod markup; mod pretty; mod span; mod token; -pub mod visit; + +use std::fmt; +use std::fmt::{Debug, Display, Formatter}; +use std::mem; +use std::rc::Rc; pub use expr::*; pub use ident::*; @@ -14,3 +18,685 @@ pub use markup::*; pub use pretty::*; pub use span::*; pub use token::*; + +use crate::geom::{AngularUnit, LengthUnit}; +use crate::source::SourceId; +use crate::util::EcoString; + +#[derive(Debug, Clone, PartialEq)] +pub enum NodeKind { + /// A left square bracket: `[`. + LeftBracket, + /// A right square bracket: `]`. + RightBracket, + /// A left curly brace: `{`. + LeftBrace, + /// A right curly brace: `}`. + RightBrace, + /// A left round parenthesis: `(`. + LeftParen, + /// A right round parenthesis: `)`. + RightParen, + /// An asterisk: `*`. + Star, + /// A comma: `,`. + Comma, + /// A semicolon: `;`. + Semicolon, + /// A colon: `:`. + Colon, + /// A plus: `+`. + Plus, + /// A hyphen: `-`. + Minus, + /// A slash: `/`. + Slash, + /// A single equals sign: `=`. + Eq, + /// Two equals signs: `==`. + EqEq, + /// An exclamation mark followed by an equals sign: `!=`. + ExclEq, + /// A less-than sign: `<`. + Lt, + /// A less-than sign followed by an equals sign: `<=`. + LtEq, + /// A greater-than sign: `>`. + Gt, + /// A greater-than sign followed by an equals sign: `>=`. + GtEq, + /// A plus followed by an equals sign: `+=`. + PlusEq, + /// A hyphen followed by an equals sign: `-=`. + HyphEq, + /// An asterisk followed by an equals sign: `*=`. + StarEq, + /// A slash followed by an equals sign: `/=`. + SlashEq, + /// Two dots: `..`. + Dots, + /// An equals sign followed by a greater-than sign: `=>`. + Arrow, + /// The `not` operator. + Not, + /// The `and` operator. + And, + /// The `or` operator. + Or, + /// The `with` operator. + With, + /// The `with` expression: `with (1)`. + WithExpr, + /// The none literal: `none`. + None, + /// The auto literal: `auto`. + Auto, + /// The `let` keyword. + Let, + /// The `if` keyword. + If, + /// The `else` keyword. + Else, + /// The `for` keyword. + For, + /// The `in` keyword. + In, + /// The `while` keyword. + While, + /// The `break` keyword. + Break, + /// The `continue` keyword. + Continue, + /// The `return` keyword. + Return, + /// The `import` keyword. + Import, + /// The `include` keyword. + Include, + /// The `from` keyword. + From, + /// One or more whitespace characters. + Space(usize), + /// A consecutive non-markup string. + Text(EcoString), + /// A slash and the letter "u" followed by a hexadecimal unicode entity + /// enclosed in curly braces: `\u{1F5FA}`. + UnicodeEscape(UnicodeEscapeToken), + /// An arbitrary number of backticks followed by inner contents, terminated + /// with the same number of backticks: `` `...` ``. + Raw(RawToken), + /// Dollar signs surrounding inner contents. + Math(MathToken), + /// A numbering: `23.`. + /// + /// Can also exist without the number: `.`. + EnumNumbering(Option<usize>), + /// An identifier: `center`. + Ident(EcoString), + /// A boolean: `true`, `false`. + Bool(bool), + /// An integer: `120`. + Int(i64), + /// A floating-point number: `1.2`, `10e-4`. + Float(f64), + /// A length: `12pt`, `3cm`. + Length(f64, LengthUnit), + /// An angle: `90deg`. + Angle(f64, AngularUnit), + /// A percentage: `50%`. + /// + /// _Note_: `50%` is stored as `50.0` here, as in the corresponding + /// [literal](super::Lit::Percent). + Percentage(f64), + /// A fraction unit: `3fr`. + Fraction(f64), + /// A quoted string: `"..."`. + Str(StrToken), + /// Two slashes followed by inner contents, terminated with a newline: + /// `//<str>\n`. + LineComment, + /// A slash and a star followed by inner contents, terminated with a star + /// and a slash: `/*<str>*/`. + /// + /// The comment can contain nested block comments. + BlockComment, + /// A node that should never appear in a finished tree. + Never, + /// Tokens that appear in the wrong place. + Error(ErrorPosition, EcoString), + /// Template markup. + Markup, + /// A forced line break: `\`. + Linebreak, + /// A paragraph break: Two or more newlines. + Parbreak, + /// Strong text was enabled / disabled: `*`. + Strong, + /// Emphasized text was enabled / disabled: `_`. + Emph, + /// A non-breaking space: `~`. + NonBreakingSpace, + /// An en-dash: `--`. + EnDash, + /// An em-dash: `---`. + EmDash, + /// A section heading: `= Introduction`. + Heading, + /// A heading's level: `=`, `==`, `===`, etc. + HeadingLevel(u8), + /// An item in an unordered list: `- ...`. + List, + /// The bullet character of an item in an unordered list: `-`. + ListBullet, + /// An item in an enumeration (ordered list): `1. ...`. + Enum, + /// An array expression: `(1, "hi", 12cm)`. + Array, + /// A dictionary expression: `(thickness: 3pt, pattern: dashed)`. + Dict, + /// A named argument: `thickness: 3pt`. + Named, + /// A template expression: `[*Hi* there!]`. + Template, + /// A grouped expression: `(1 + 2)`. + Group, + /// A block expression: `{ let x = 1; x + 2 }`. + Block, + /// A unary operation: `-x`. + Unary, + /// A binary operation: `a + b`. + Binary, + /// An invocation of a function: `f(x, y)`. + Call, + /// A function call's argument list: `(x, y)`. + CallArgs, + /// A closure expression: `(x, y) => z`. + Closure, + /// A closure's parameters: `(x, y)`. + ClosureParams, + /// A parameter sink: `..x`. + ParameterSink, + /// A for loop expression: `for x in y { ... }`. + ForExpr, + /// A while loop expression: `while x { ... }`. + WhileExpr, + /// An if expression: `if x { ... }`. + IfExpr, + /// A let expression: `let x = 1`. + LetExpr, + /// A for loop's destructuring pattern: `x` or `x, y`. + ForPattern, + /// The import expression: `import x from "foo.typ"`. + ImportExpr, + /// Items to import: `a, b, c`. + ImportItems, + /// The include expression: `include "foo.typ"`. + IncludeExpr, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ErrorPosition { + /// At the start of the node. + Start, + /// Over the full width of the node. + Full, + /// At the end of the node. + End, +} + +impl Display for NodeKind { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.pad(match self { + Self::LeftBracket => "opening bracket", + Self::RightBracket => "closing bracket", + Self::LeftBrace => "opening brace", + Self::RightBrace => "closing brace", + Self::LeftParen => "opening paren", + Self::RightParen => "closing paren", + Self::Star => "star", + Self::Comma => "comma", + Self::Semicolon => "semicolon", + Self::Colon => "colon", + Self::Plus => "plus", + Self::Minus => "minus", + Self::Slash => "slash", + Self::Eq => "assignment operator", + Self::EqEq => "equality operator", + Self::ExclEq => "inequality operator", + Self::Lt => "less-than operator", + Self::LtEq => "less-than or equal operator", + Self::Gt => "greater-than operator", + Self::GtEq => "greater-than or equal operator", + Self::PlusEq => "add-assign operator", + Self::HyphEq => "subtract-assign operator", + Self::StarEq => "multiply-assign operator", + Self::SlashEq => "divide-assign operator", + Self::Dots => "dots", + Self::Arrow => "arrow", + Self::Not => "operator `not`", + Self::And => "operator `and`", + Self::Or => "operator `or`", + Self::With => "operator `with`", + Self::WithExpr => "`with` expression", + Self::None => "`none`", + Self::Auto => "`auto`", + Self::Let => "keyword `let`", + Self::If => "keyword `if`", + Self::Else => "keyword `else`", + Self::For => "keyword `for`", + Self::In => "keyword `in`", + Self::While => "keyword `while`", + Self::Break => "keyword `break`", + Self::Continue => "keyword `continue`", + Self::Return => "keyword `return`", + Self::Import => "keyword `import`", + Self::Include => "keyword `include`", + Self::From => "keyword `from`", + Self::Space(_) => "space", + Self::Math(_) => "math formula", + Self::EnumNumbering(_) => "numbering", + Self::Str(_) => "string", + Self::Never => "a node that should not be here", + Self::LineComment => "line comment", + Self::BlockComment => "block comment", + Self::Markup => "markup", + Self::Linebreak => "forced linebreak", + Self::Parbreak => "paragraph break", + Self::Strong => "strong", + Self::Emph => "emphasis", + Self::Text(_) => "text", + Self::NonBreakingSpace => "non-breaking space", + Self::EnDash => "en dash", + Self::EmDash => "em dash", + Self::UnicodeEscape(_) => "unicode escape sequence", + Self::Raw(_) => "raw block", + Self::Heading => "heading", + Self::HeadingLevel(_) => "heading level", + Self::List => "list", + Self::ListBullet => "list bullet", + Self::Enum => "enum", + Self::Ident(_) => "identifier", + Self::Bool(_) => "boolean", + Self::Int(_) => "integer", + Self::Float(_) => "float", + Self::Length(_, _) => "length", + Self::Angle(_, _) => "angle", + Self::Percentage(_) => "percentage", + Self::Fraction(_) => "`fr` value", + Self::Array => "array", + Self::Dict => "dictionary", + Self::Named => "named argument", + Self::Template => "template", + Self::Group => "group", + Self::Block => "block", + Self::Unary => "unary expression", + Self::Binary => "binary expression", + Self::Call => "call", + Self::CallArgs => "call arguments", + Self::Closure => "closure", + Self::ClosureParams => "closure parameters", + Self::ParameterSink => "parameter sink", + Self::ForExpr => "for-loop expression", + Self::WhileExpr => "while-loop expression", + Self::IfExpr => "if expression", + Self::LetExpr => "let expression", + Self::ForPattern => "for-loop destructuring pattern", + Self::ImportExpr => "import expression", + Self::ImportItems => "import items", + Self::IncludeExpr => "include expression", + Self::Error(_, src) => match src.as_str() { + "*/" => "end of block comment", + _ => "invalid token", + }, + }) + } +} + +impl NodeKind { + pub fn is_parenthesis(&self) -> bool { + match self { + Self::LeftParen => true, + Self::RightParen => true, + _ => false, + } + } + + pub fn is_bracket(&self) -> bool { + match self { + Self::LeftBracket => true, + Self::RightBracket => true, + _ => false, + } + } + + pub fn is_brace(&self) -> bool { + match self { + Self::LeftBrace => true, + Self::RightBrace => true, + _ => false, + } + } + + pub fn is_error(&self) -> bool { + matches!(self, NodeKind::Never | NodeKind::Error(_, _)) + } +} + +/// A syntactical node. +#[derive(Clone, PartialEq)] +pub struct GreenNode { + /// Node metadata. + meta: GreenData, + /// This node's children, losslessly make up this node. + children: Vec<Green>, +} + +/// Data shared between [`GreenNode`]s and [`GreenToken`]s. +#[derive(Clone, PartialEq)] +pub struct GreenData { + /// What kind of node this is (each kind would have its own struct in a + /// strongly typed AST). + kind: NodeKind, + /// The byte length of the node in the source. + len: usize, + /// Whether this node or any of its children are erroneous. + has_error: bool, +} + +impl GreenData { + pub fn new(kind: NodeKind, len: usize) -> Self { + Self { len, has_error: kind.is_error(), kind } + } + + pub fn kind(&self) -> &NodeKind { + &self.kind + } + + pub fn len(&self) -> usize { + self.len + } + + pub fn has_error(&self) -> bool { + self.has_error + } +} + +impl From<GreenData> for Green { + fn from(token: GreenData) -> Self { + Self::Token(token) + } +} + +/// Children of a [`GreenNode`]. +#[derive(Clone, PartialEq)] +pub enum Green { + /// A terminal owned token. + Token(GreenData), + /// A non-terminal node in an Rc. + Node(Rc<GreenNode>), +} + +impl Green { + fn meta(&self) -> &GreenData { + match self { + Green::Token(t) => &t, + Green::Node(n) => &n.meta, + } + } + + pub fn kind(&self) -> &NodeKind { + self.meta().kind() + } + + pub fn len(&self) -> usize { + self.meta().len() + } + + pub fn has_error(&self) -> bool { + self.meta().has_error() + } + + pub fn children(&self) -> &[Green] { + match self { + Green::Token(_) => &[], + Green::Node(n) => &n.children(), + } + } +} + +impl GreenNode { + pub fn new(kind: NodeKind, len: usize) -> Self { + Self { + meta: GreenData::new(kind, len), + children: Vec::new(), + } + } + + pub fn with_children( + kind: NodeKind, + len: usize, + children: impl Iterator<Item = impl Into<Green>>, + ) -> Self { + let mut meta = GreenData::new(kind, len); + let children = children + .map(|x| { + let x = x.into(); + meta.has_error |= x.has_error(); + x + }) + .collect(); + Self { meta, children } + } + + pub fn with_child(kind: NodeKind, len: usize, child: impl Into<Green>) -> Self { + Self::with_children(kind, len, std::iter::once(child.into())) + } + + pub fn children(&self) -> &[Green] { + &self.children + } +} + +impl From<GreenNode> for Green { + fn from(node: GreenNode) -> Self { + Rc::new(node).into() + } +} + +impl From<Rc<GreenNode>> for Green { + fn from(node: Rc<GreenNode>) -> Self { + Self::Node(node) + } +} + +impl Default for Green { + fn default() -> Self { + Self::Token(GreenData::new(NodeKind::Never, 0)) + } +} + +impl Debug for Green { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{:?}: {}", self.kind(), self.len())?; + if let Self::Node(n) = self { + if !n.children.is_empty() { + f.write_str(" ")?; + f.debug_list().entries(&n.children).finish()?; + } + } + + Ok(()) + } +} + +#[derive(Copy, Clone, PartialEq)] +pub struct RedTicket<'a> { + id: SourceId, + offset: usize, + green: &'a Green, +} + +impl<'a> RedTicket<'a> { + pub fn own(self) -> RedNode { + RedNode { + id: self.id, + offset: self.offset, + green: self.green.clone(), + } + } + + pub fn kind(&self) -> &NodeKind { + self.green.kind() + } + + + pub fn cast<T>(self) -> Option<T> + where + T: TypedNode, + { + T::cast_from(self) + } +} + +#[derive(Clone, PartialEq)] +pub struct RedNode { + id: SourceId, + offset: usize, + green: Green, +} + +impl RedNode { + pub fn new_root(root: Rc<GreenNode>, id: SourceId) -> Self { + Self { id, offset: 0, green: root.into() } + } + + pub fn span(&self) -> Span { + Span::new(self.id, self.offset, self.offset + self.green.len()) + } + + pub fn len(&self) -> usize { + self.green.len() + } + + pub fn kind(&self) -> &NodeKind { + self.green.kind() + } + + pub fn children<'a>(&'a self) -> impl Iterator<Item = RedTicket<'a>> + Clone + 'a { + let children = match &self.green { + Green::Node(node) => node.children(), + Green::Token(_) => &[], + }; + + let mut offset = self.offset; + children.iter().map(move |green_child| { + let child_offset = offset; + offset += green_child.len(); + RedTicket { + id: self.id, + offset: child_offset, + green: &green_child, + } + }) + } + + pub fn has_error(&self) -> bool { + self.green.has_error() + } + + pub fn errors(&self) -> Vec<(Span, EcoString)> { + if !self.green.has_error() { + return vec![]; + } + + if let NodeKind::Error(pos, msg) = self.kind() { + let span = match pos { + ErrorPosition::Start => self.span().at_start(), + ErrorPosition::Full => self.span(), + ErrorPosition::End => self.span().at_end(), + }; + + vec![(span, msg.clone())] + } else if let NodeKind::Never = self.kind() { + vec![(self.span(), "found a never node".into())] + } else { + self.children() + .filter(|ticket| ticket.green.has_error()) + .flat_map(|ticket| ticket.own().errors()) + .collect() + } + } + + pub fn ticket<'a>(&'a self) -> RedTicket<'a> { + RedTicket { + id: self.id, + offset: self.offset, + green: &self.green, + } + } + + pub(crate) fn typed_child(&self, kind: &NodeKind) -> Option<RedNode> { + self.children() + .find(|x| mem::discriminant(x.kind()) == mem::discriminant(kind)) + .map(RedTicket::own) + } + + pub(crate) fn cast_first_child<T: TypedNode>(&self) -> Option<T> { + self.children().find_map(RedTicket::cast) + } + + pub(crate) fn cast_last_child<T: TypedNode>(&self) -> Option<T> { + self.children().filter_map(RedTicket::cast).last() + } +} + +impl Debug for RedNode { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{:?}: {:?}", self.kind(), self.span())?; + let children = self.children().collect::<Vec<_>>(); + if !children.is_empty() { + f.write_str(" ")?; + f.debug_list() + .entries(children.into_iter().map(RedTicket::own)) + .finish()?; + } + Ok(()) + } +} + +pub trait TypedNode: Sized { + /// Performs the conversion. + fn cast_from(value: RedTicket) -> Option<Self>; +} + +#[macro_export] +macro_rules! node { + (#[doc = $doc:expr] $name:ident) => { + node!(#[doc = $doc] $name => $name); + }; + (#[doc = $doc:expr] $variant:ident => $name:ident) => { + #[doc = $doc] + #[derive(Debug, Clone, PartialEq)] + pub struct $name(RedNode); + + impl TypedNode for $name { + fn cast_from(node: RedTicket) -> Option<Self> { + if node.kind() != &NodeKind::$variant { + return None; + } + + Some(Self(node.own())) + } + } + + impl $name { + pub fn span(&self) -> Span { + self.0.span() + } + + pub fn underlying(&self) -> RedTicket { + self.0.ticket() + } + } + }; +} diff --git a/src/syntax/pretty.rs b/src/syntax/pretty.rs index 3d02f39f..b1c7e02b 100644 --- a/src/syntax/pretty.rs +++ b/src/syntax/pretty.rs @@ -88,10 +88,10 @@ impl Pretty for MarkupNode { match self { // TODO: Handle escaping. Self::Space => p.push(' '), - Self::Linebreak(_) => p.push_str(r"\"), - Self::Parbreak(_) => p.push_str("\n\n"), - Self::Strong(_) => p.push('*'), - Self::Emph(_) => p.push('_'), + Self::Linebreak => p.push_str(r"\"), + Self::Parbreak => p.push_str("\n\n"), + Self::Strong => p.push('*'), + Self::Emph => p.push('_'), Self::Text(text) => p.push_str(text), Self::Raw(raw) => raw.pretty(p), Self::Heading(heading) => heading.pretty(p), @@ -165,28 +165,28 @@ impl Pretty for RawNode { impl Pretty for HeadingNode { fn pretty(&self, p: &mut Printer) { - for _ in 0 .. self.level { + for _ in 0 .. self.level().0 { p.push('='); } p.push(' '); - self.body.pretty(p); + self.body().pretty(p); } } impl Pretty for ListNode { fn pretty(&self, p: &mut Printer) { p.push_str("- "); - self.body.pretty(p); + self.body().pretty(p); } } impl Pretty for EnumNode { fn pretty(&self, p: &mut Printer) { - if let Some(number) = self.number { + if let Some(number) = self.number().0 { write!(p, "{}", number).unwrap(); } p.push_str(". "); - self.body.pretty(p); + self.body().pretty(p); } } @@ -235,8 +235,10 @@ impl Pretty for Lit { impl Pretty for ArrayExpr { fn pretty(&self, p: &mut Printer) { p.push('('); - p.join(&self.items, ", ", |item, p| item.pretty(p)); - if self.items.len() == 1 { + + let items = self.items(); + p.join(&items, ", ", |item, p| item.pretty(p)); + if items.len() == 1 { p.push(','); } p.push(')'); @@ -246,10 +248,12 @@ impl Pretty for ArrayExpr { impl Pretty for DictExpr { fn pretty(&self, p: &mut Printer) { p.push('('); - if self.items.is_empty() { + + let items = self.items(); + if items.is_empty() { p.push(':'); } else { - p.join(&self.items, ", ", |named, p| named.pretty(p)); + p.join(&items, ", ", |named, p| named.pretty(p)); } p.push(')'); } @@ -257,16 +261,16 @@ impl Pretty for DictExpr { impl Pretty for Named { fn pretty(&self, p: &mut Printer) { - self.name.pretty(p); + self.name().pretty(p); p.push_str(": "); - self.expr.pretty(p); + self.expr().pretty(p); } } impl Pretty for TemplateExpr { fn pretty(&self, p: &mut Printer) { p.push('['); - self.body.pretty(p); + self.body().pretty(p); p.push(']'); } } @@ -274,7 +278,7 @@ impl Pretty for TemplateExpr { impl Pretty for GroupExpr { fn pretty(&self, p: &mut Printer) { p.push('('); - self.expr.pretty(p); + self.expr().pretty(p); p.push(')'); } } @@ -282,11 +286,13 @@ impl Pretty for GroupExpr { impl Pretty for BlockExpr { fn pretty(&self, p: &mut Printer) { p.push('{'); - if self.exprs.len() > 1 { + + let exprs = self.exprs(); + if exprs.len() > 1 { p.push(' '); } - p.join(&self.exprs, "; ", |expr, p| expr.pretty(p)); - if self.exprs.len() > 1 { + p.join(&exprs, "; ", |expr, p| expr.pretty(p)); + if exprs.len() > 1 { p.push(' '); } p.push('}'); @@ -295,11 +301,12 @@ impl Pretty for BlockExpr { impl Pretty for UnaryExpr { fn pretty(&self, p: &mut Printer) { - self.op.pretty(p); - if self.op == UnOp::Not { + let op = self.op(); + op.pretty(p); + if op == UnOp::Not { p.push(' '); } - self.expr.pretty(p); + self.expr().pretty(p); } } @@ -311,11 +318,11 @@ impl Pretty for UnOp { impl Pretty for BinaryExpr { fn pretty(&self, p: &mut Printer) { - self.lhs.pretty(p); + self.lhs().pretty(p); p.push(' '); - self.op.pretty(p); + self.op().pretty(p); p.push(' '); - self.rhs.pretty(p); + self.rhs().pretty(p); } } @@ -327,7 +334,7 @@ impl Pretty for BinOp { impl Pretty for CallExpr { fn pretty(&self, p: &mut Printer) { - self.callee.pretty(p); + self.callee().pretty(p); let mut write_args = |items: &[CallArg]| { p.push('('); @@ -335,25 +342,26 @@ impl Pretty for CallExpr { p.push(')'); }; - match self.args.items.as_slice() { - // This can be moved behind the arguments. - // - // Example: Transforms "#v(a, [b])" => "#v(a)[b]". - [head @ .., CallArg::Pos(Expr::Template(template))] => { - if !head.is_empty() { - write_args(head); - } - template.pretty(p); - } + let arg_list = self.args(); + let args = arg_list.items(); - items => write_args(items), + if let Some(Expr::Template(template)) = args + .last() + .and_then(|x| if let CallArg::Pos(arg) = x { Some(arg) } else { None }) + { + if args.len() > 1 { + write_args(&args[0 .. args.len() - 1]); + } + template.pretty(p); + } else { + write_args(&args); } } } impl Pretty for CallArgs { fn pretty(&self, p: &mut Printer) { - p.join(&self.items, ", ", |item, p| item.pretty(p)); + p.join(&self.items(), ", ", |item, p| item.pretty(p)); } } @@ -372,15 +380,15 @@ impl Pretty for CallArg { impl Pretty for ClosureExpr { fn pretty(&self, p: &mut Printer) { - if let [param] = self.params.as_slice() { + if let [param] = self.params().as_slice() { param.pretty(p); } else { p.push('('); - p.join(self.params.iter(), ", ", |item, p| item.pretty(p)); + p.join(self.params().iter(), ", ", |item, p| item.pretty(p)); p.push(')'); } p.push_str(" => "); - self.body.pretty(p); + self.body().pretty(p); } } @@ -399,9 +407,9 @@ impl Pretty for ClosureParam { impl Pretty for WithExpr { fn pretty(&self, p: &mut Printer) { - self.callee.pretty(p); + self.callee().pretty(p); p.push_str(" with ("); - self.args.pretty(p); + self.args().pretty(p); p.push(')'); } } @@ -409,13 +417,13 @@ impl Pretty for WithExpr { impl Pretty for LetExpr { fn pretty(&self, p: &mut Printer) { p.push_str("let "); - self.binding.pretty(p); - if let Some(Expr::Closure(closure)) = &self.init { + self.binding().pretty(p); + if let Some(Expr::Closure(closure)) = &self.init() { p.push('('); - p.join(closure.params.iter(), ", ", |item, p| item.pretty(p)); + p.join(closure.params().iter(), ", ", |item, p| item.pretty(p)); p.push_str(") = "); - closure.body.pretty(p); - } else if let Some(init) = &self.init { + closure.body().pretty(p); + } else if let Some(init) = &self.init() { p.push_str(" = "); init.pretty(p); } @@ -425,10 +433,10 @@ impl Pretty for LetExpr { impl Pretty for IfExpr { fn pretty(&self, p: &mut Printer) { p.push_str("if "); - self.condition.pretty(p); + self.condition().pretty(p); p.push(' '); - self.if_body.pretty(p); - if let Some(expr) = &self.else_body { + self.if_body().pretty(p); + if let Some(expr) = &self.else_body() { p.push_str(" else "); expr.pretty(p); } @@ -438,42 +446,40 @@ impl Pretty for IfExpr { impl Pretty for WhileExpr { fn pretty(&self, p: &mut Printer) { p.push_str("while "); - self.condition.pretty(p); + self.condition().pretty(p); p.push(' '); - self.body.pretty(p); + self.body().pretty(p); } } impl Pretty for ForExpr { fn pretty(&self, p: &mut Printer) { p.push_str("for "); - self.pattern.pretty(p); + self.pattern().pretty(p); p.push_str(" in "); - self.iter.pretty(p); + self.iter().pretty(p); p.push(' '); - self.body.pretty(p); + self.body().pretty(p); } } impl Pretty for ForPattern { fn pretty(&self, p: &mut Printer) { - match self { - Self::Value(v) => v.pretty(p), - Self::KeyValue(k, v) => { - k.pretty(p); - p.push_str(", "); - v.pretty(p); - } + if let Some(key) = self.key() { + key.pretty(p); + p.push_str(", "); } + + self.value().pretty(p); } } impl Pretty for ImportExpr { fn pretty(&self, p: &mut Printer) { p.push_str("import "); - self.imports.pretty(p); + self.imports().pretty(p); p.push_str(" from "); - self.path.pretty(p); + self.path().pretty(p); } } @@ -489,7 +495,7 @@ impl Pretty for Imports { impl Pretty for IncludeExpr { fn pretty(&self, p: &mut Printer) { p.push_str("include "); - self.path.pretty(p); + self.path().pretty(p); } } @@ -502,7 +508,6 @@ impl Pretty for Ident { #[cfg(test)] mod tests { use super::*; - use crate::parse::parse; use crate::source::SourceFile; #[track_caller] @@ -513,7 +518,7 @@ mod tests { #[track_caller] fn test_parse(src: &str, expected: &str) { let source = SourceFile::detached(src); - let ast = parse(&source).unwrap(); + let ast: Markup = source.ast().unwrap(); let found = pretty(&ast); if found != expected { println!("tree: {:#?}", ast); diff --git a/src/syntax/span.rs b/src/syntax/span.rs index bfb9e755..ee7cba4c 100644 --- a/src/syntax/span.rs +++ b/src/syntax/span.rs @@ -109,6 +109,11 @@ impl Span { *self = self.join(other) } + /// Test whether a position is within the span. + pub fn contains_pos(&self, pos: Pos) -> bool { + self.start <= pos && self.end >= pos + } + /// Test whether one span complete contains the other span. pub fn contains(self, other: Self) -> bool { self.source == other.source && self.start <= other.start && self.end >= other.end @@ -118,6 +123,16 @@ impl Span { pub fn to_range(self) -> Range<usize> { self.start.to_usize() .. self.end.to_usize() } + + /// A new span at the position of this span's start. + pub fn at_start(&self) -> Span { + Self::at(self.source, self.start) + } + + /// A new span at the position of this span's end. + pub fn at_end(&self) -> Span { + Self::at(self.source, self.end) + } } impl Debug for Span { diff --git a/src/syntax/token.rs b/src/syntax/token.rs index 22dd104b..49613667 100644 --- a/src/syntax/token.rs +++ b/src/syntax/token.rs @@ -1,188 +1,38 @@ -use crate::geom::{AngularUnit, LengthUnit}; - -/// A minimal semantic entity of source code. -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum Token<'s> { - /// A left square bracket: `[`. - LeftBracket, - /// A right square bracket: `]`. - RightBracket, - /// A left curly brace: `{`. - LeftBrace, - /// A right curly brace: `}`. - RightBrace, - /// A left round parenthesis: `(`. - LeftParen, - /// A right round parenthesis: `)`. - RightParen, - /// An asterisk: `*`. - Star, - /// An underscore: `_`. - Underscore, - /// A tilde: `~`. - Tilde, - /// Two hyphens: `--`. - HyphHyph, - /// Three hyphens: `---`. - HyphHyphHyph, - /// A backslash followed by nothing or whitespace: `\`. - Backslash, - /// A comma: `,`. - Comma, - /// A semicolon: `;`. - Semicolon, - /// A colon: `:`. - Colon, - /// A plus: `+`. - Plus, - /// A hyphen: `-`. - Hyph, - /// A slash: `/`. - Slash, - /// A single equals sign: `=`. - Eq, - /// Two equals signs: `==`. - EqEq, - /// An exclamation mark followed by an equals sign: `!=`. - ExclEq, - /// A less-than sign: `<`. - Lt, - /// A less-than sign followed by an equals sign: `<=`. - LtEq, - /// A greater-than sign: `>`. - Gt, - /// A greater-than sign followed by an equals sign: `>=`. - GtEq, - /// A plus followed by an equals sign: `+=`. - PlusEq, - /// A hyphen followed by an equals sign: `-=`. - HyphEq, - /// An asterisk followed by an equals sign: `*=`. - StarEq, - /// A slash followed by an equals sign: `/=`. - SlashEq, - /// Two dots: `..`. - Dots, - /// An equals sign followed by a greater-than sign: `=>`. - Arrow, - /// The `not` operator. - Not, - /// The `and` operator. - And, - /// The `or` operator. - Or, - /// The `with` operator. - With, - /// The none literal: `none`. - None, - /// The auto literal: `auto`. - Auto, - /// The `let` keyword. - Let, - /// The `if` keyword. - If, - /// The `else` keyword. - Else, - /// The `for` keyword. - For, - /// The `in` keyword. - In, - /// The `while` keyword. - While, - /// The `break` keyword. - Break, - /// The `continue` keyword. - Continue, - /// The `return` keyword. - Return, - /// The `import` keyword. - Import, - /// The `include` keyword. - Include, - /// The `from` keyword. - From, - /// One or more whitespace characters. - /// - /// The contained `usize` denotes the number of newlines that were contained - /// in the whitespace. - Space(usize), - /// A consecutive non-markup string. - Text(&'s str), - /// A slash and the letter "u" followed by a hexadecimal unicode entity - /// enclosed in curly braces: `\u{1F5FA}`. - UnicodeEscape(UnicodeEscapeToken<'s>), - /// An arbitrary number of backticks followed by inner contents, terminated - /// with the same number of backticks: `` `...` ``. - Raw(RawToken<'s>), - /// One or two dollar signs followed by inner contents, terminated with the - /// same number of dollar signs. - Math(MathToken<'s>), - /// A numbering: `23.`. - /// - /// Can also exist without the number: `.`. - Numbering(Option<usize>), - /// An identifier: `center`. - Ident(&'s str), - /// A boolean: `true`, `false`. - Bool(bool), - /// An integer: `120`. - Int(i64), - /// A floating-point number: `1.2`, `10e-4`. - Float(f64), - /// A length: `12pt`, `3cm`. - Length(f64, LengthUnit), - /// An angle: `90deg`. - Angle(f64, AngularUnit), - /// A percentage: `50%`. - /// - /// _Note_: `50%` is stored as `50.0` here, as in the corresponding - /// [literal](super::Lit::Percent). - Percent(f64), - /// A fraction unit: `3fr`. - Fraction(f64), - /// A quoted string: `"..."`. - Str(StrToken<'s>), - /// Two slashes followed by inner contents, terminated with a newline: - /// `//<str>\n`. - LineComment(&'s str), - /// A slash and a star followed by inner contents, terminated with a star - /// and a slash: `/*<str>*/`. - /// - /// The comment can contain nested block comments. - BlockComment(&'s str), - /// Things that are not valid tokens. - Invalid(&'s str), -} +use crate::util::EcoString; /// A quoted string token: `"..."`. -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct StrToken<'s> { +#[derive(Debug, Clone, PartialEq)] +pub struct StrToken { /// The string inside the quotes. /// /// _Note_: If the string contains escape sequences these are not yet /// applied to be able to just store a string slice here instead of /// a `String`. The resolving is done later in the parser. - pub string: &'s str, + pub string: EcoString, /// Whether the closing quote was present. pub terminated: bool, } /// A raw block token: `` `...` ``. -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct RawToken<'s> { - /// The raw text between the backticks. - pub text: &'s str, +#[derive(Debug, Clone, PartialEq)] +pub struct RawToken { + /// The raw text in the block. + pub text: EcoString, + /// The programming language of the raw text. + pub lang: Option<EcoString>, /// The number of opening backticks. - pub backticks: usize, + pub backticks: u8, /// Whether all closing backticks were present. pub terminated: bool, + /// Whether to display this as a block. + pub block: bool, } /// A math formula token: `$2pi + x$` or `$[f'(x) = x^2]$`. -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct MathToken<'s> { +#[derive(Debug, Clone, PartialEq)] +pub struct MathToken { /// The formula between the dollars. - pub formula: &'s str, + pub formula: EcoString, /// Whether the formula is display-level, that is, it is surrounded by /// `$[..]`. pub display: bool, @@ -191,86 +41,21 @@ pub struct MathToken<'s> { } /// A unicode escape sequence token: `\u{1F5FA}`. -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct UnicodeEscapeToken<'s> { +#[derive(Debug, Clone, PartialEq)] +pub struct UnicodeEscapeToken { /// The escape sequence between the braces. - pub sequence: &'s str, + pub sequence: EcoString, + /// The resulting unicode character. + pub character: Option<char>, /// Whether the closing brace was present. pub terminated: bool, } -impl<'s> Token<'s> { - /// The English name of this token for use in error messages. - pub fn name(self) -> &'static str { - match self { - Self::LeftBracket => "opening bracket", - Self::RightBracket => "closing bracket", - Self::LeftBrace => "opening brace", - Self::RightBrace => "closing brace", - Self::LeftParen => "opening paren", - Self::RightParen => "closing paren", - Self::Star => "star", - Self::Underscore => "underscore", - Self::Tilde => "tilde", - Self::HyphHyph => "en dash", - Self::HyphHyphHyph => "em dash", - Self::Backslash => "backslash", - Self::Comma => "comma", - Self::Semicolon => "semicolon", - Self::Colon => "colon", - Self::Plus => "plus", - Self::Hyph => "minus", - Self::Slash => "slash", - Self::Eq => "assignment operator", - Self::EqEq => "equality operator", - Self::ExclEq => "inequality operator", - Self::Lt => "less-than operator", - Self::LtEq => "less-than or equal operator", - Self::Gt => "greater-than operator", - Self::GtEq => "greater-than or equal operator", - Self::PlusEq => "add-assign operator", - Self::HyphEq => "subtract-assign operator", - Self::StarEq => "multiply-assign operator", - Self::SlashEq => "divide-assign operator", - Self::Dots => "dots", - Self::Arrow => "arrow", - Self::Not => "operator `not`", - Self::And => "operator `and`", - Self::Or => "operator `or`", - Self::With => "operator `with`", - Self::None => "`none`", - Self::Auto => "`auto`", - Self::Let => "keyword `let`", - Self::If => "keyword `if`", - Self::Else => "keyword `else`", - Self::For => "keyword `for`", - Self::In => "keyword `in`", - Self::While => "keyword `while`", - Self::Break => "keyword `break`", - Self::Continue => "keyword `continue`", - Self::Return => "keyword `return`", - Self::Import => "keyword `import`", - Self::Include => "keyword `include`", - Self::From => "keyword `from`", - Self::Space(_) => "space", - Self::Text(_) => "text", - Self::UnicodeEscape(_) => "unicode escape sequence", - Self::Raw(_) => "raw block", - Self::Math(_) => "math formula", - Self::Numbering(_) => "numbering", - Self::Ident(_) => "identifier", - Self::Bool(_) => "boolean", - Self::Int(_) => "integer", - Self::Float(_) => "float", - Self::Length(_, _) => "length", - Self::Angle(_, _) => "angle", - Self::Percent(_) => "percentage", - Self::Fraction(_) => "`fr` value", - Self::Str(_) => "string", - Self::LineComment(_) => "line comment", - Self::BlockComment(_) => "block comment", - Self::Invalid("*/") => "end of block comment", - Self::Invalid(_) => "invalid token", - } - } +/// A unit-bound number token: `1.2em`. +#[derive(Debug, Clone, PartialEq)] +pub struct UnitToken { + /// The number part. + pub number: std::ops::Range<usize>, + /// The unit part. + pub unit: std::ops::Range<usize>, } diff --git a/src/syntax/visit.rs b/src/syntax/visit.rs deleted file mode 100644 index 40e8eb93..00000000 --- a/src/syntax/visit.rs +++ /dev/null @@ -1,263 +0,0 @@ -//! Mutable and immutable syntax tree traversal. - -use super::*; - -/// Implement the immutable and the mutable visitor version. -macro_rules! impl_visitors { - ($($name:ident($($tts:tt)*) $body:block)*) => { - macro_rules! r { - (rc: $x:expr) => { $x.as_ref() }; - ($x:expr) => { &$x }; - } - - impl_visitor! { - Visit, - immutable, - immutably, - [$(($name($($tts)*) $body))*] - } - - macro_rules! r { - (rc: $x:expr) => { std::rc::Rc::make_mut(&mut $x) }; - ($x:expr) => { &mut $x }; - } - - impl_visitor! { - VisitMut, - mutable, - mutably, - [$(($name($($tts)*) $body mut))*] mut - } - }; -} - -/// Implement an immutable or mutable visitor. -macro_rules! impl_visitor { - ( - $visit:ident, - $mutability:ident, - $adjective:ident, - [$(( - $name:ident($v:ident, $node:ident: $ty:ty) - $body:block - $($fmut:tt)? - ))*] - $($mut:tt)? - ) => { - #[doc = concat!("Visit syntax trees ", stringify!($adjective), ".")] - pub trait $visit<'ast> { - /// Visit a definition of a binding. - /// - /// Bindings are, for example, left-hand side of let expressions, - /// and key/value patterns in for loops. - fn visit_binding(&mut self, _: &'ast $($mut)? Ident) {} - - /// Visit the entry into a scope. - fn visit_enter(&mut self) {} - - /// Visit the exit from a scope. - fn visit_exit(&mut self) {} - - $(fn $name(&mut self, $node: &'ast $($fmut)? $ty) { - $mutability::$name(self, $node); - })* - } - - #[doc = concat!("Visitor functions that are ", stringify!($mutability), ".")] - pub mod $mutability { - use super::*; - $( - #[doc = concat!("Visit a node of type [`", stringify!($ty), "`].")] - pub fn $name<'ast, V>($v: &mut V, $node: &'ast $($fmut)? $ty) - where - V: $visit<'ast> + ?Sized - $body - )* - } - }; -} - -impl_visitors! { - visit_tree(v, markup: Markup) { - for node in markup { - v.visit_node(node); - } - } - - visit_node(v, node: MarkupNode) { - match node { - MarkupNode::Space => {} - MarkupNode::Linebreak(_) => {} - MarkupNode::Parbreak(_) => {} - MarkupNode::Strong(_) => {} - MarkupNode::Emph(_) => {} - MarkupNode::Text(_) => {} - MarkupNode::Raw(_) => {} - MarkupNode::Heading(n) => v.visit_heading(n), - MarkupNode::List(n) => v.visit_list(n), - MarkupNode::Enum(n) => v.visit_enum(n), - MarkupNode::Expr(n) => v.visit_expr(n), - } - } - - visit_heading(v, heading: HeadingNode) { - v.visit_tree(r!(heading.body)); - } - - visit_list(v, list: ListNode) { - v.visit_tree(r!(list.body)); - } - - visit_enum(v, enum_: EnumNode) { - v.visit_tree(r!(enum_.body)); - } - - visit_expr(v, expr: Expr) { - match expr { - Expr::Ident(_) => {} - Expr::Lit(_) => {}, - Expr::Array(e) => v.visit_array(e), - Expr::Dict(e) => v.visit_dict(e), - Expr::Template(e) => v.visit_template(e), - Expr::Group(e) => v.visit_group(e), - Expr::Block(e) => v.visit_block(e), - Expr::Unary(e) => v.visit_unary(e), - Expr::Binary(e) => v.visit_binary(e), - Expr::Call(e) => v.visit_call(e), - Expr::Closure(e) => v.visit_closure(e), - Expr::With(e) => v.visit_with(e), - Expr::Let(e) => v.visit_let(e), - Expr::If(e) => v.visit_if(e), - Expr::While(e) => v.visit_while(e), - Expr::For(e) => v.visit_for(e), - Expr::Import(e) => v.visit_import(e), - Expr::Include(e) => v.visit_include(e), - } - } - - visit_array(v, array: ArrayExpr) { - for expr in r!(array.items) { - v.visit_expr(expr); - } - } - - visit_dict(v, dict: DictExpr) { - for named in r!(dict.items) { - v.visit_expr(r!(named.expr)); - } - } - - visit_template(v, template: TemplateExpr) { - v.visit_enter(); - v.visit_tree(r!(template.body)); - v.visit_exit(); - } - - visit_group(v, group: GroupExpr) { - v.visit_expr(r!(group.expr)); - } - - visit_block(v, block: BlockExpr) { - v.visit_enter(); - for expr in r!(block.exprs) { - v.visit_expr(expr); - } - v.visit_exit(); - } - - visit_binary(v, binary: BinaryExpr) { - v.visit_expr(r!(binary.lhs)); - v.visit_expr(r!(binary.rhs)); - } - - visit_unary(v, unary: UnaryExpr) { - v.visit_expr(r!(unary.expr)); - } - - visit_call(v, call: CallExpr) { - v.visit_expr(r!(call.callee)); - v.visit_args(r!(call.args)); - } - - visit_args(v, args: CallArgs) { - for arg in r!(args.items) { - v.visit_arg(arg); - } - } - - visit_arg(v, arg: CallArg) { - match arg { - CallArg::Pos(expr) => v.visit_expr(expr), - CallArg::Named(named) => v.visit_expr(r!(named.expr)), - CallArg::Spread(expr) => v.visit_expr(expr), - } - } - - visit_closure(v, closure: ClosureExpr) { - for param in r!(closure.params) { - v.visit_param(param); - } - v.visit_expr(r!(rc: closure.body)); - } - - visit_param(v, param: ClosureParam) { - match param { - ClosureParam::Pos(binding) => v.visit_binding(binding), - ClosureParam::Named(named) => { - v.visit_binding(r!(named.name)); - v.visit_expr(r!(named.expr)); - } - ClosureParam::Sink(binding) => v.visit_binding(binding), - } - } - - visit_with(v, with_expr: WithExpr) { - v.visit_expr(r!(with_expr.callee)); - v.visit_args(r!(with_expr.args)); - } - - visit_let(v, let_expr: LetExpr) { - if let Some(init) = r!(let_expr.init) { - v.visit_expr(init); - } - v.visit_binding(r!(let_expr.binding)); - } - - visit_if(v, if_expr: IfExpr) { - v.visit_expr(r!(if_expr.condition)); - v.visit_expr(r!(if_expr.if_body)); - if let Some(body) = r!(if_expr.else_body) { - v.visit_expr(body); - } - } - - visit_while(v, while_expr: WhileExpr) { - v.visit_expr(r!(while_expr.condition)); - v.visit_expr(r!(while_expr.body)); - } - - visit_for(v, for_expr: ForExpr) { - v.visit_expr(r!(for_expr.iter)); - match r!(for_expr.pattern) { - ForPattern::Value(value) => v.visit_binding(value), - ForPattern::KeyValue(key, value) => { - v.visit_binding(key); - v.visit_binding(value); - } - } - v.visit_expr(r!(for_expr.body)); - } - - visit_import(v, import_expr: ImportExpr) { - v.visit_expr(r!(import_expr.path)); - if let Imports::Idents(idents) = r!(import_expr.imports) { - for ident in idents { - v.visit_binding(ident); - } - } - } - - visit_include(v, include_expr: IncludeExpr) { - v.visit_expr(r!(include_expr.path)); - } -} |
