summaryrefslogtreecommitdiff
path: root/src/syntax
diff options
context:
space:
mode:
Diffstat (limited to 'src/syntax')
-rw-r--r--src/syntax/expr.rs772
-rw-r--r--src/syntax/ident.rs12
-rw-r--r--src/syntax/markup.rs176
-rw-r--r--src/syntax/mod.rs688
-rw-r--r--src/syntax/pretty.rs143
-rw-r--r--src/syntax/span.rs15
-rw-r--r--src/syntax/token.rs271
-rw-r--r--src/syntax/visit.rs263
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));
- }
-}