Remove most fields from SyntaxKind enum

This commit is contained in:
Laurenz 2023-01-15 12:00:13 +01:00
parent 15f0434d1f
commit 40561e57fb
21 changed files with 2062 additions and 2340 deletions

View File

@ -208,6 +208,6 @@ fn items() -> LangItems {
math_atom: |atom| math::AtomNode(atom).pack(), math_atom: |atom| math::AtomNode(atom).pack(),
math_script: |base, sub, sup| math::ScriptNode { base, sub, sup }.pack(), math_script: |base, sub, sup| math::ScriptNode { base, sub, sup }.pack(),
math_frac: |num, denom| math::FracNode { num, denom }.pack(), math_frac: |num, denom| math::FracNode { num, denom }.pack(),
math_align_point: |count| math::AlignPointNode(count).pack(), math_align_point: || math::AlignPointNode.pack(),
} }
} }

View File

@ -637,12 +637,12 @@ impl Texify for ScriptNode {
#[func] #[func]
#[capable(Texify)] #[capable(Texify)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct AlignPointNode(pub NonZeroUsize); pub struct AlignPointNode;
#[node] #[node]
impl AlignPointNode { impl AlignPointNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { fn construct(_: &Vm, _: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("index")?).pack()) Ok(Self.pack())
} }
} }

View File

@ -50,7 +50,7 @@ pub type SourceResult<T> = Result<T, Box<Vec<SourceError>>>;
/// An error in a source file. /// An error in a source file.
/// ///
/// This contained spans will only be detached if any of the input source files /// The contained spans will only be detached if any of the input source files
/// were detached. /// were detached.
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct SourceError { pub struct SourceError {

View File

@ -138,7 +138,7 @@ fn complete_params(ctx: &mut CompletionContext) -> bool {
(SyntaxKind::Colon, _) => prev.prev_leaf(), (SyntaxKind::Colon, _) => prev.prev_leaf(),
_ => None, _ => None,
}; };
if let SyntaxKind::Ident(param) = before_colon.kind(); if let Some(param) = before_colon.cast::<ast::Ident>();
then { then {
ctx.from = match ctx.leaf.kind() { ctx.from = match ctx.leaf.kind() {
SyntaxKind::Colon | SyntaxKind::Space { .. } => ctx.cursor, SyntaxKind::Colon | SyntaxKind::Space { .. } => ctx.cursor,
@ -160,11 +160,11 @@ fn complete_params(ctx: &mut CompletionContext) -> bool {
deciding.kind(), deciding.kind(),
SyntaxKind::LeftParen SyntaxKind::LeftParen
| SyntaxKind::Comma | SyntaxKind::Comma
| SyntaxKind::Ident(_) | SyntaxKind::Ident
); );
then { then {
ctx.from = match deciding.kind() { ctx.from = match deciding.kind() {
SyntaxKind::Ident(_) => deciding.offset(), SyntaxKind::Ident => deciding.offset(),
_ => ctx.cursor, _ => ctx.cursor,
}; };
@ -192,9 +192,9 @@ fn complete_symbols(ctx: &mut CompletionContext) -> bool {
// Behind half-completed symbol: "$arrow:|$". // Behind half-completed symbol: "$arrow:|$".
if_chain! { if_chain! {
if matches!(ctx.leaf.kind(), SyntaxKind::Atom(s) if s == ":"); if matches!(ctx.leaf.kind(), SyntaxKind::Atom if ctx.leaf.text() == ":");
if let Some(prev) = ctx.leaf.prev_leaf(); if let Some(prev) = ctx.leaf.prev_leaf();
if matches!(prev.kind(), SyntaxKind::Ident(_)); if matches!(prev.kind(), SyntaxKind::Ident);
then { then {
ctx.from = prev.offset(); ctx.from = prev.offset();
ctx.symbol_completions(false); ctx.symbol_completions(false);
@ -205,7 +205,7 @@ fn complete_symbols(ctx: &mut CompletionContext) -> bool {
// Start of a symbol: ":|". // Start of a symbol: ":|".
// Checking for a text node ensures that "\:" isn't completed. // Checking for a text node ensures that "\:" isn't completed.
if ctx.before.ends_with(':') if ctx.before.ends_with(':')
&& matches!(ctx.leaf.kind(), SyntaxKind::Text(_) | SyntaxKind::Atom(_)) && matches!(ctx.leaf.kind(), SyntaxKind::Text | SyntaxKind::Atom)
{ {
ctx.from = ctx.cursor; ctx.from = ctx.cursor;
ctx.symbol_completions(needs_colon); ctx.symbol_completions(needs_colon);
@ -213,7 +213,7 @@ fn complete_symbols(ctx: &mut CompletionContext) -> bool {
} }
// An existing symbol: ":arrow:". // An existing symbol: ":arrow:".
if matches!(ctx.leaf.kind(), SyntaxKind::Symbol(_)) { if matches!(ctx.leaf.kind(), SyntaxKind::Symbol) {
// We want to complete behind the colon, therefore plus 1. // We want to complete behind the colon, therefore plus 1.
let has_colon = ctx.after.starts_with(':'); let has_colon = ctx.after.starts_with(':');
ctx.from = ctx.leaf.offset() + (has_colon as usize); ctx.from = ctx.leaf.offset() + (has_colon as usize);
@ -225,12 +225,12 @@ fn complete_symbols(ctx: &mut CompletionContext) -> bool {
if_chain! { if_chain! {
if matches!( if matches!(
ctx.leaf.kind(), ctx.leaf.kind(),
SyntaxKind::Text(_) | SyntaxKind::Atom(_) | SyntaxKind::Ident(_) SyntaxKind::Text | SyntaxKind::Atom | SyntaxKind::Ident
); );
if let Some(prev) = ctx.leaf.prev_leaf(); if let Some(prev) = ctx.leaf.prev_leaf();
if matches!(prev.kind(), SyntaxKind::Symbol(_)) || matches!( if matches!(prev.kind(), SyntaxKind::Symbol) || matches!(
prev.kind(), prev.kind(),
SyntaxKind::Text(s) | SyntaxKind::Atom(s) if s == ":" SyntaxKind::Text | SyntaxKind::Atom if prev.text() == ":"
); );
then { then {
// We want to complete behind the colon, therefore plus 1. // We want to complete behind the colon, therefore plus 1.
@ -252,14 +252,14 @@ fn complete_markup(ctx: &mut CompletionContext) -> bool {
// Start of an interpolated identifier: "#|". // Start of an interpolated identifier: "#|".
// Checking for a text node ensures that "\#" isn't completed. // Checking for a text node ensures that "\#" isn't completed.
if ctx.before.ends_with('#') && matches!(ctx.leaf.kind(), SyntaxKind::Text(_)) { if ctx.before.ends_with('#') && matches!(ctx.leaf.kind(), SyntaxKind::Text) {
ctx.from = ctx.cursor; ctx.from = ctx.cursor;
ctx.expr_completions(true); ctx.expr_completions(true);
return true; return true;
} }
// An existing identifier: "#pa|". // An existing identifier: "#pa|".
if matches!(ctx.leaf.kind(), SyntaxKind::Ident(_)) { if matches!(ctx.leaf.kind(), SyntaxKind::Ident) {
// We want to complete behind the hashtag, therefore plus 1. // We want to complete behind the hashtag, therefore plus 1.
ctx.from = ctx.leaf.offset() + 1; ctx.from = ctx.leaf.offset() + 1;
ctx.expr_completions(true); ctx.expr_completions(true);
@ -298,14 +298,14 @@ fn complete_math(ctx: &mut CompletionContext) -> bool {
} }
// Start of an interpolated identifier: "#|". // Start of an interpolated identifier: "#|".
if matches!(ctx.leaf.kind(), SyntaxKind::Atom(s) if s == "#") { if matches!(ctx.leaf.kind(), SyntaxKind::Atom if ctx.leaf.text() == "#") {
ctx.from = ctx.cursor; ctx.from = ctx.cursor;
ctx.expr_completions(true); ctx.expr_completions(true);
return true; return true;
} }
// Behind existing atom or identifier: "$a|$" or "$abc|$". // Behind existing atom or identifier: "$a|$" or "$abc|$".
if matches!(ctx.leaf.kind(), SyntaxKind::Atom(_) | SyntaxKind::Ident(_)) { if matches!(ctx.leaf.kind(), SyntaxKind::Atom | SyntaxKind::Ident) {
ctx.from = ctx.leaf.offset(); ctx.from = ctx.leaf.offset();
ctx.math_completions(); ctx.math_completions();
return true; return true;
@ -331,7 +331,7 @@ fn complete_code(ctx: &mut CompletionContext) -> bool {
} }
// An existing identifier: "{ pa| }". // An existing identifier: "{ pa| }".
if matches!(ctx.leaf.kind(), SyntaxKind::Ident(_)) { if matches!(ctx.leaf.kind(), SyntaxKind::Ident) {
ctx.from = ctx.leaf.offset(); ctx.from = ctx.leaf.offset();
ctx.expr_completions(false); ctx.expr_completions(false);
return true; return true;

View File

@ -119,7 +119,6 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
_ => Category::Operator, _ => Category::Operator,
}), }),
SyntaxKind::Hat => Some(Category::MathOperator), SyntaxKind::Hat => Some(Category::MathOperator),
SyntaxKind::Amp => Some(Category::MathOperator),
SyntaxKind::Dot => Some(Category::Punctuation), SyntaxKind::Dot => Some(Category::Punctuation),
SyntaxKind::Eq => match node.parent_kind() { SyntaxKind::Eq => match node.parent_kind() {
Some(SyntaxKind::Heading) => None, Some(SyntaxKind::Heading) => None,
@ -159,38 +158,38 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
SyntaxKind::As => Some(Category::Keyword), SyntaxKind::As => Some(Category::Keyword),
SyntaxKind::Markup { .. } SyntaxKind::Markup { .. }
if node.parent_kind() == Some(&SyntaxKind::TermItem) if node.parent_kind() == Some(SyntaxKind::TermItem)
&& node.next_sibling().as_ref().map(|v| v.kind()) && node.next_sibling().as_ref().map(|v| v.kind())
== Some(&SyntaxKind::Colon) => == Some(SyntaxKind::Colon) =>
{ {
Some(Category::ListTerm) Some(Category::ListTerm)
} }
SyntaxKind::Markup { .. } => None, SyntaxKind::Markup { .. } => None,
SyntaxKind::Text(_) => None, SyntaxKind::Text => None,
SyntaxKind::Linebreak => Some(Category::Escape), SyntaxKind::Linebreak => Some(Category::Escape),
SyntaxKind::Escape(_) => Some(Category::Escape), SyntaxKind::Escape => Some(Category::Escape),
SyntaxKind::Shorthand(_) => Some(Category::Escape), SyntaxKind::Shorthand => Some(Category::Escape),
SyntaxKind::Symbol(_) => Some(Category::Escape), SyntaxKind::Symbol => Some(Category::Escape),
SyntaxKind::SmartQuote { .. } => None, SyntaxKind::SmartQuote { .. } => None,
SyntaxKind::Strong => Some(Category::Strong), SyntaxKind::Strong => Some(Category::Strong),
SyntaxKind::Emph => Some(Category::Emph), SyntaxKind::Emph => Some(Category::Emph),
SyntaxKind::Raw(_) => Some(Category::Raw), SyntaxKind::Raw { .. } => Some(Category::Raw),
SyntaxKind::Link(_) => Some(Category::Link), SyntaxKind::Link => Some(Category::Link),
SyntaxKind::Label(_) => Some(Category::Label), SyntaxKind::Label => Some(Category::Label),
SyntaxKind::Ref(_) => Some(Category::Ref), SyntaxKind::Ref => Some(Category::Ref),
SyntaxKind::Heading => Some(Category::Heading), SyntaxKind::Heading => Some(Category::Heading),
SyntaxKind::ListItem => None, SyntaxKind::ListItem => None,
SyntaxKind::EnumItem => None, SyntaxKind::EnumItem => None,
SyntaxKind::EnumNumbering(_) => Some(Category::ListMarker), SyntaxKind::EnumNumbering => Some(Category::ListMarker),
SyntaxKind::TermItem => None, SyntaxKind::TermItem => None,
SyntaxKind::Math => None, SyntaxKind::Math => None,
SyntaxKind::Atom(_) => None, SyntaxKind::Atom => None,
SyntaxKind::Script => None, SyntaxKind::Script => None,
SyntaxKind::Frac => None, SyntaxKind::Frac => None,
SyntaxKind::AlignPoint => None, SyntaxKind::AlignPoint => Some(Category::MathOperator),
SyntaxKind::Ident(_) => match node.parent_kind() { SyntaxKind::Ident => match node.parent_kind() {
Some( Some(
SyntaxKind::Markup { .. } SyntaxKind::Markup { .. }
| SyntaxKind::Math | SyntaxKind::Math
@ -202,9 +201,9 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
if node if node
.parent() .parent()
.and_then(|p| p.parent()) .and_then(|p| p.parent())
.filter(|gp| gp.kind() == &SyntaxKind::Parenthesized) .filter(|gp| gp.kind() == SyntaxKind::Parenthesized)
.and_then(|gp| gp.parent()) .and_then(|gp| gp.parent())
.map_or(false, |ggp| ggp.kind() == &SyntaxKind::FuncCall) .map_or(false, |ggp| ggp.kind() == SyntaxKind::FuncCall)
&& node.next_sibling().is_none() => && node.next_sibling().is_none() =>
{ {
Some(Category::Function) Some(Category::Function)
@ -218,17 +217,17 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
Some(SyntaxKind::SetRule) => Some(Category::Function), Some(SyntaxKind::SetRule) => Some(Category::Function),
Some(SyntaxKind::ShowRule) Some(SyntaxKind::ShowRule)
if node.prev_sibling().as_ref().map(|v| v.kind()) if node.prev_sibling().as_ref().map(|v| v.kind())
== Some(&SyntaxKind::Show) => == Some(SyntaxKind::Show) =>
{ {
Some(Category::Function) Some(Category::Function)
} }
_ => None, _ => None,
}, },
SyntaxKind::Bool(_) => Some(Category::Keyword), SyntaxKind::Bool => Some(Category::Keyword),
SyntaxKind::Int(_) => Some(Category::Number), SyntaxKind::Int => Some(Category::Number),
SyntaxKind::Float(_) => Some(Category::Number), SyntaxKind::Float => Some(Category::Number),
SyntaxKind::Numeric(_, _) => Some(Category::Number), SyntaxKind::Numeric => Some(Category::Number),
SyntaxKind::Str(_) => Some(Category::String), SyntaxKind::Str => Some(Category::String),
SyntaxKind::CodeBlock => None, SyntaxKind::CodeBlock => None,
SyntaxKind::ContentBlock => None, SyntaxKind::ContentBlock => None,
SyntaxKind::Parenthesized => None, SyntaxKind::Parenthesized => None,
@ -259,7 +258,7 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
SyntaxKind::LoopContinue => None, SyntaxKind::LoopContinue => None,
SyntaxKind::FuncReturn => None, SyntaxKind::FuncReturn => None,
SyntaxKind::Error(_, _) => Some(Category::Error), SyntaxKind::Error => Some(Category::Error),
} }
} }

View File

@ -18,12 +18,12 @@ pub fn tooltip(world: &dyn World, source: &Source, cursor: usize) -> Option<Stri
/// Tooltip for a function or set rule name. /// Tooltip for a function or set rule name.
fn function_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<String> { fn function_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<String> {
if_chain! { if_chain! {
if let SyntaxKind::Ident(ident) = leaf.kind(); if let Some(ident) = leaf.cast::<ast::Ident>();
if matches!( if matches!(
leaf.parent_kind(), leaf.parent_kind(),
Some(SyntaxKind::FuncCall | SyntaxKind::SetRule), Some(SyntaxKind::FuncCall | SyntaxKind::SetRule),
); );
if let Some(Value::Func(func)) = world.library().scope.get(ident); if let Some(Value::Func(func)) = world.library().scope.get(&ident);
if let Some(info) = func.info(); if let Some(info) = func.info();
then { then {
return Some(plain_docs_sentence(&info.docs)); return Some(plain_docs_sentence(&info.docs));
@ -60,8 +60,8 @@ fn named_param_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<String> {
// Hovering over the parameter name. // Hovering over the parameter name.
if_chain! { if_chain! {
if leaf.index() == 0; if leaf.index() == 0;
if let SyntaxKind::Ident(ident) = leaf.kind(); if let Some(ident) = leaf.cast::<ast::Ident>();
if let Some(param) = info.param(ident); if let Some(param) = info.param(&ident);
then { then {
return Some(plain_docs_sentence(param.docs)); return Some(plain_docs_sentence(param.docs));
} }
@ -69,9 +69,9 @@ fn named_param_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<String> {
// Hovering over a string parameter value. // Hovering over a string parameter value.
if_chain! { if_chain! {
if let SyntaxKind::Str(string) = leaf.kind(); if let Some(string) = leaf.cast::<ast::Str>();
if let Some(param) = info.param(&named.name()); if let Some(param) = info.param(&named.name());
if let Some(docs) = find_string_doc(&param.cast, string); if let Some(docs) = find_string_doc(&param.cast, &string.get());
then { then {
return Some(docs.into()); return Some(docs.into());
} }
@ -95,8 +95,8 @@ fn find_string_doc(info: &CastInfo, string: &str) -> Option<&'static str> {
fn font_family_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<String> { fn font_family_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<String> {
if_chain! { if_chain! {
// Ensure that we are on top of a string. // Ensure that we are on top of a string.
if let SyntaxKind::Str(string) = leaf.kind(); if let Some(string) = leaf.cast::<ast::Str>();
let lower = string.to_lowercase(); let lower = string.get().to_lowercase();
// Ensure that we are in the arguments to the text function. // Ensure that we are in the arguments to the text function.
if let Some(parent) = leaf.parent(); if let Some(parent) = leaf.parent();

View File

@ -16,8 +16,8 @@ use crate::diag::{
}; };
use crate::geom::{Abs, Angle, Em, Fr, Ratio}; use crate::geom::{Abs, Angle, Em, Fr, Ratio};
use crate::syntax::ast::AstNode; use crate::syntax::ast::AstNode;
use crate::syntax::{ast, Source, SourceId, Span, Spanned, SyntaxKind, SyntaxNode, Unit}; use crate::syntax::{ast, Source, SourceId, Span, Spanned, SyntaxKind, SyntaxNode};
use crate::util::PathExt; use crate::util::{EcoString, PathExt};
use crate::World; use crate::World;
const MAX_ITERATIONS: usize = 10_000; const MAX_ITERATIONS: usize = 10_000;
@ -389,13 +389,13 @@ impl Eval for ast::Symbol {
type Output = Content; type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok((vm.items.symbol)(self.get().clone())) Ok((vm.items.symbol)(self.get().into()))
} }
} }
impl ast::Symbol { impl ast::Symbol {
fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> { fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
Ok((vm.items.symbol)(self.get().clone() + ":op".into())) Ok((vm.items.symbol)(EcoString::from(self.get()) + ":op".into()))
} }
} }
@ -427,8 +427,8 @@ impl Eval for ast::Raw {
type Output = Content; type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let text = self.text().clone(); let text = self.text();
let lang = self.lang().cloned(); let lang = self.lang().map(Into::into);
let block = self.block(); let block = self.block();
Ok((vm.items.raw)(text, lang, block)) Ok((vm.items.raw)(text, lang, block))
} }
@ -446,7 +446,7 @@ impl Eval for ast::Label {
type Output = Value; type Output = Value;
fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
Ok(Value::Label(Label(self.get().clone()))) Ok(Value::Label(Label(self.get().into())))
} }
} }
@ -454,7 +454,7 @@ impl Eval for ast::Ref {
type Output = Content; type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok((vm.items.ref_)(self.get().clone())) Ok((vm.items.ref_)(self.get().into()))
} }
} }
@ -542,7 +542,7 @@ impl Eval for ast::AlignPoint {
type Output = Content; type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok((vm.items.math_align_point)(self.count())) Ok((vm.items.math_align_point)())
} }
} }
@ -563,7 +563,7 @@ impl ast::Ident {
if self.as_untyped().len() == self.len() if self.as_untyped().len() == self.len()
&& matches!(vm.scopes.get(&self), Ok(Value::Func(_)) | Err(_)) && matches!(vm.scopes.get(&self), Ok(Value::Func(_)) | Err(_))
{ {
Ok((vm.items.symbol)(self.get().clone() + ":op".into())) Ok((vm.items.symbol)(EcoString::from(self.get()) + ":op".into()))
} else { } else {
Ok(self.eval(vm)?.display_in_math()) Ok(self.eval(vm)?.display_in_math())
} }
@ -616,11 +616,11 @@ impl Eval for ast::Numeric {
fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
let (v, unit) = self.get(); let (v, unit) = self.get();
Ok(match unit { Ok(match unit {
Unit::Length(unit) => Abs::with_unit(v, unit).into(), ast::Unit::Length(unit) => Abs::with_unit(v, unit).into(),
Unit::Angle(unit) => Angle::with_unit(v, unit).into(), ast::Unit::Angle(unit) => Angle::with_unit(v, unit).into(),
Unit::Em => Em::new(v).into(), ast::Unit::Em => Em::new(v).into(),
Unit::Fr => Fr::new(v).into(), ast::Unit::Fr => Fr::new(v).into(),
Unit::Percent => Ratio::new(v / 100.0).into(), ast::Unit::Percent => Ratio::new(v / 100.0).into(),
}) })
} }
} }
@ -743,7 +743,7 @@ impl Eval for ast::Dict {
map.insert(named.name().take().into(), named.expr().eval(vm)?); map.insert(named.name().take().into(), named.expr().eval(vm)?);
} }
ast::DictItem::Keyed(keyed) => { ast::DictItem::Keyed(keyed) => {
map.insert(keyed.key().into(), keyed.expr().eval(vm)?); map.insert(keyed.key().get().into(), keyed.expr().eval(vm)?);
} }
ast::DictItem::Spread(expr) => match expr.eval(vm)? { ast::DictItem::Spread(expr) => match expr.eval(vm)? {
Value::None => {} Value::None => {}

View File

@ -74,8 +74,8 @@ pub struct LangItems {
fn(base: Content, sub: Option<Content>, sup: Option<Content>) -> Content, fn(base: Content, sub: Option<Content>, sup: Option<Content>) -> Content,
/// A fraction in a formula: `x/2`. /// A fraction in a formula: `x/2`.
pub math_frac: fn(num: Content, denom: Content) -> Content, pub math_frac: fn(num: Content, denom: Content) -> Content,
/// An alignment point in a formula: `&`, `&&`. /// An alignment point in a formula: `&`.
pub math_align_point: fn(count: NonZeroUsize) -> Content, pub math_align_point: fn() -> Content,
} }
impl Debug for LangItems { impl Debug for LangItems {

View File

@ -5,7 +5,12 @@
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
use std::ops::Deref; use std::ops::Deref;
use super::{RawFields, Span, SyntaxKind, SyntaxNode, Unit}; use unscanny::Scanner;
use super::{
is_id_continue, is_id_start, is_newline, split_newlines, Span, SyntaxKind, SyntaxNode,
};
use crate::geom::{AbsUnit, AngleUnit};
use crate::util::EcoString; use crate::util::EcoString;
/// A typed AST node. /// A typed AST node.
@ -117,7 +122,7 @@ pub enum Expr {
Script(Script), Script(Script),
/// A fraction in a math formula: `x/2`. /// A fraction in a math formula: `x/2`.
Frac(Frac), Frac(Frac),
/// An alignment point in a math formula: `&`, `&&`. /// An alignment point in a math formula: `&`.
AlignPoint(AlignPoint), AlignPoint(AlignPoint),
/// An identifier: `left`. /// An identifier: `left`.
Ident(Ident), Ident(Ident),
@ -194,34 +199,34 @@ impl AstNode for Expr {
fn from_untyped(node: &SyntaxNode) -> Option<Self> { fn from_untyped(node: &SyntaxNode) -> Option<Self> {
match node.kind() { match node.kind() {
SyntaxKind::Linebreak => node.cast().map(Self::Linebreak), SyntaxKind::Linebreak => node.cast().map(Self::Linebreak),
SyntaxKind::Text(_) => node.cast().map(Self::Text), SyntaxKind::Text => node.cast().map(Self::Text),
SyntaxKind::Escape(_) => node.cast().map(Self::Escape), SyntaxKind::Escape => node.cast().map(Self::Escape),
SyntaxKind::Shorthand(_) => node.cast().map(Self::Shorthand), SyntaxKind::Shorthand => node.cast().map(Self::Shorthand),
SyntaxKind::Symbol(_) => node.cast().map(Self::Symbol), SyntaxKind::Symbol => node.cast().map(Self::Symbol),
SyntaxKind::SmartQuote { .. } => node.cast().map(Self::SmartQuote), SyntaxKind::SmartQuote { .. } => node.cast().map(Self::SmartQuote),
SyntaxKind::Strong => node.cast().map(Self::Strong), SyntaxKind::Strong => node.cast().map(Self::Strong),
SyntaxKind::Emph => node.cast().map(Self::Emph), SyntaxKind::Emph => node.cast().map(Self::Emph),
SyntaxKind::Raw(_) => node.cast().map(Self::Raw), SyntaxKind::Raw { .. } => node.cast().map(Self::Raw),
SyntaxKind::Link(_) => node.cast().map(Self::Link), SyntaxKind::Link => node.cast().map(Self::Link),
SyntaxKind::Label(_) => node.cast().map(Self::Label), SyntaxKind::Label => node.cast().map(Self::Label),
SyntaxKind::Ref(_) => node.cast().map(Self::Ref), SyntaxKind::Ref => node.cast().map(Self::Ref),
SyntaxKind::Heading => node.cast().map(Self::Heading), SyntaxKind::Heading => node.cast().map(Self::Heading),
SyntaxKind::ListItem => node.cast().map(Self::List), SyntaxKind::ListItem => node.cast().map(Self::List),
SyntaxKind::EnumItem => node.cast().map(Self::Enum), SyntaxKind::EnumItem => node.cast().map(Self::Enum),
SyntaxKind::TermItem => node.cast().map(Self::Term), SyntaxKind::TermItem => node.cast().map(Self::Term),
SyntaxKind::Math => node.cast().map(Self::Math), SyntaxKind::Math => node.cast().map(Self::Math),
SyntaxKind::Atom(_) => node.cast().map(Self::Atom), SyntaxKind::Atom => node.cast().map(Self::Atom),
SyntaxKind::Script => node.cast().map(Self::Script), SyntaxKind::Script => node.cast().map(Self::Script),
SyntaxKind::Frac => node.cast().map(Self::Frac), SyntaxKind::Frac => node.cast().map(Self::Frac),
SyntaxKind::AlignPoint => node.cast().map(Self::AlignPoint), SyntaxKind::AlignPoint => node.cast().map(Self::AlignPoint),
SyntaxKind::Ident(_) => node.cast().map(Self::Ident), SyntaxKind::Ident => node.cast().map(Self::Ident),
SyntaxKind::None => node.cast().map(Self::None), SyntaxKind::None => node.cast().map(Self::None),
SyntaxKind::Auto => node.cast().map(Self::Auto), SyntaxKind::Auto => node.cast().map(Self::Auto),
SyntaxKind::Bool(_) => node.cast().map(Self::Bool), SyntaxKind::Bool => node.cast().map(Self::Bool),
SyntaxKind::Int(_) => node.cast().map(Self::Int), SyntaxKind::Int => node.cast().map(Self::Int),
SyntaxKind::Float(_) => node.cast().map(Self::Float), SyntaxKind::Float => node.cast().map(Self::Float),
SyntaxKind::Numeric(_, _) => node.cast().map(Self::Numeric), SyntaxKind::Numeric => node.cast().map(Self::Numeric),
SyntaxKind::Str(_) => node.cast().map(Self::Str), SyntaxKind::Str => node.cast().map(Self::Str),
SyntaxKind::CodeBlock => node.cast().map(Self::Code), SyntaxKind::CodeBlock => node.cast().map(Self::Code),
SyntaxKind::ContentBlock => node.cast().map(Self::Content), SyntaxKind::ContentBlock => node.cast().map(Self::Content),
SyntaxKind::Parenthesized => node.cast().map(Self::Parenthesized), SyntaxKind::Parenthesized => node.cast().map(Self::Parenthesized),
@ -315,7 +320,7 @@ impl Space {
/// Get the number of newlines. /// Get the number of newlines.
pub fn newlines(&self) -> usize { pub fn newlines(&self) -> usize {
match self.0.kind() { match self.0.kind() {
&SyntaxKind::Space { newlines } => newlines, SyntaxKind::Space { newlines } => newlines,
_ => panic!("space is of wrong kind"), _ => panic!("space is of wrong kind"),
} }
} }
@ -334,10 +339,7 @@ node! {
impl Text { impl Text {
/// Get the text. /// Get the text.
pub fn get(&self) -> &EcoString { pub fn get(&self) -> &EcoString {
match self.0.kind() { self.0.text()
SyntaxKind::Text(v) => v,
_ => panic!("text is of wrong kind"),
}
} }
} }
@ -349,15 +351,22 @@ node! {
impl Escape { impl Escape {
/// Get the escaped character. /// Get the escaped character.
pub fn get(&self) -> char { pub fn get(&self) -> char {
match self.0.kind() { let mut s = Scanner::new(self.0.text());
&SyntaxKind::Escape(v) => v, s.expect('\\');
_ => panic!("escape is of wrong kind"), if s.eat_if("u{") {
let hex = s.eat_while(char::is_ascii_hexdigit);
u32::from_str_radix(hex, 16)
.ok()
.and_then(std::char::from_u32)
.expect("unicode escape is invalid")
} else {
s.eat().expect("escape is missing escaped character")
} }
} }
} }
node! { node! {
/// A shorthand for a unicode codepoint. For example, `~` for non-breaking /// A shorthand for a unicode codepoint. For example, `~` for a non-breaking
/// space or `-?` for a soft hyphen. /// space or `-?` for a soft hyphen.
Shorthand Shorthand
} }
@ -365,9 +374,26 @@ node! {
impl Shorthand { impl Shorthand {
/// Get the shorthanded character. /// Get the shorthanded character.
pub fn get(&self) -> char { pub fn get(&self) -> char {
match self.0.kind() { match self.0.text().as_str() {
&SyntaxKind::Shorthand(v) => v, "~" => '\u{00A0}',
_ => panic!("shorthand is of wrong kind"), "..." => '\u{2026}',
"--" => '\u{2013}',
"---" => '\u{2014}',
"-?" => '\u{00AD}',
"!=" => '≠',
"<=" => '≤',
">=" => '≥',
"<-" => '←',
"->" => '→',
"=>" => '⇒',
":=" => '≔',
"[|" => '⟦',
"|]" => '⟧',
"||" => '‖',
"|->" => '↦',
"<->" => '↔',
"<=>" => '⇔',
_ => panic!("shorthand is invalid"),
} }
} }
} }
@ -379,11 +405,8 @@ node! {
impl Symbol { impl Symbol {
/// Get the symbol's notation. /// Get the symbol's notation.
pub fn get(&self) -> &EcoString { pub fn get(&self) -> &str {
match self.0.kind() { self.0.text().trim_matches(':')
SyntaxKind::Symbol(v) => v,
_ => panic!("symbol is of wrong kind"),
}
} }
} }
@ -395,10 +418,7 @@ node! {
impl SmartQuote { impl SmartQuote {
/// Whether this is a double quote. /// Whether this is a double quote.
pub fn double(&self) -> bool { pub fn double(&self) -> bool {
match self.0.kind() { self.0.text() == "\""
&SyntaxKind::SmartQuote { double } => double,
_ => panic!("smart quote is of wrong kind"),
}
} }
} }
@ -410,7 +430,7 @@ node! {
impl Strong { impl Strong {
/// The contents of the strong node. /// The contents of the strong node.
pub fn body(&self) -> Markup { pub fn body(&self) -> Markup {
self.0.cast_first_child().expect("strong node is missing markup body") self.0.cast_first_match().expect("strong emphasis is missing body")
} }
} }
@ -422,9 +442,7 @@ node! {
impl Emph { impl Emph {
/// The contents of the emphasis node. /// The contents of the emphasis node.
pub fn body(&self) -> Markup { pub fn body(&self) -> Markup {
self.0 self.0.cast_first_match().expect("emphasis is missing body")
.cast_first_child()
.expect("emphasis node is missing markup body")
} }
} }
@ -434,27 +452,75 @@ node! {
} }
impl Raw { impl Raw {
/// The raw text. /// The trimmed raw text.
pub fn text(&self) -> &EcoString { pub fn text(&self) -> EcoString {
&self.get().text let SyntaxKind::Raw { column } = self.0.kind() else {
panic!("raw node is of wrong kind");
};
let mut text = self.0.text().as_str();
let blocky = text.starts_with("```");
text = text.trim_matches('`');
// Trim tag, one space at the start, and one space at the end if the
// last non-whitespace char is a backtick.
if blocky {
let mut s = Scanner::new(text);
if s.eat_if(is_id_start) {
s.eat_while(is_id_continue);
}
text = s.after();
text = text.strip_prefix(' ').unwrap_or(text);
if text.trim_end().ends_with('`') {
text = text.strip_suffix(' ').unwrap_or(text);
}
}
// Split into lines.
let mut lines = split_newlines(text);
if blocky {
// Dedent based on column, but not for the first line.
for line in lines.iter_mut().skip(1) {
let offset = line
.chars()
.take(column)
.take_while(|c| c.is_whitespace())
.map(char::len_utf8)
.sum();
*line = &line[offset..];
}
let is_whitespace = |line: &&str| line.chars().all(char::is_whitespace);
// Trims a sequence of whitespace followed by a newline at the start.
if lines.first().map_or(false, is_whitespace) {
lines.remove(0);
}
// Trims a newline followed by a sequence of whitespace at the end.
if lines.last().map_or(false, is_whitespace) {
lines.pop();
}
}
lines.join("\n").into()
} }
/// An optional identifier specifying the language to syntax-highlight in. /// An optional identifier specifying the language to syntax-highlight in.
pub fn lang(&self) -> Option<&EcoString> { pub fn lang(&self) -> Option<&str> {
self.get().lang.as_ref() let inner = self.0.text().trim_start_matches('`');
let mut s = Scanner::new(inner);
s.eat_if(is_id_start).then(|| {
s.eat_while(is_id_continue);
s.before()
})
} }
/// Whether the raw text should be displayed in a separate block. /// Whether the raw text should be displayed in a separate block.
pub fn block(&self) -> bool { pub fn block(&self) -> bool {
self.get().block let text = self.0.text();
} text.starts_with("```") && text.chars().any(is_newline)
/// The raw fields.
fn get(&self) -> &RawFields {
match self.0.kind() {
SyntaxKind::Raw(v) => v.as_ref(),
_ => panic!("raw is of wrong kind"),
}
} }
} }
@ -466,10 +532,7 @@ node! {
impl Link { impl Link {
/// Get the URL. /// Get the URL.
pub fn url(&self) -> &EcoString { pub fn url(&self) -> &EcoString {
match self.0.kind() { self.0.text()
SyntaxKind::Link(url) => url,
_ => panic!("link is of wrong kind"),
}
} }
} }
@ -480,11 +543,8 @@ node! {
impl Label { impl Label {
/// Get the label's text. /// Get the label's text.
pub fn get(&self) -> &EcoString { pub fn get(&self) -> &str {
match self.0.kind() { self.0.text().trim_start_matches('<').trim_end_matches('>')
SyntaxKind::Label(v) => v,
_ => panic!("label is of wrong kind"),
}
} }
} }
@ -495,11 +555,8 @@ node! {
impl Ref { impl Ref {
/// Get the target. /// Get the target.
pub fn get(&self) -> &EcoString { pub fn get(&self) -> &str {
match self.0.kind() { self.0.text().trim_start_matches('@')
SyntaxKind::Ref(v) => v,
_ => panic!("reference is of wrong kind"),
}
} }
} }
@ -511,14 +568,14 @@ node! {
impl Heading { impl Heading {
/// The contents of the heading. /// The contents of the heading.
pub fn body(&self) -> Markup { pub fn body(&self) -> Markup {
self.0.cast_first_child().expect("heading is missing markup body") self.0.cast_first_match().expect("heading is missing markup body")
} }
/// The section depth (numer of equals signs). /// The section depth (numer of equals signs).
pub fn level(&self) -> NonZeroUsize { pub fn level(&self) -> NonZeroUsize {
self.0 self.0
.children() .children()
.filter(|n| n.kind() == &SyntaxKind::Eq) .filter(|n| n.kind() == SyntaxKind::Eq)
.count() .count()
.try_into() .try_into()
.expect("heading is missing equals sign") .expect("heading is missing equals sign")
@ -533,7 +590,7 @@ node! {
impl ListItem { impl ListItem {
/// The contents of the list item. /// The contents of the list item.
pub fn body(&self) -> Markup { pub fn body(&self) -> Markup {
self.0.cast_first_child().expect("list item is missing body") self.0.cast_first_match().expect("list item is missing body")
} }
} }
@ -546,14 +603,14 @@ impl EnumItem {
/// The explicit numbering, if any: `23.`. /// The explicit numbering, if any: `23.`.
pub fn number(&self) -> Option<NonZeroUsize> { pub fn number(&self) -> Option<NonZeroUsize> {
self.0.children().find_map(|node| match node.kind() { self.0.children().find_map(|node| match node.kind() {
SyntaxKind::EnumNumbering(num) => Some(*num), SyntaxKind::EnumNumbering => node.text().trim_end_matches('.').parse().ok(),
_ => Option::None, _ => Option::None,
}) })
} }
/// The contents of the list item. /// The contents of the list item.
pub fn body(&self) -> Markup { pub fn body(&self) -> Markup {
self.0.cast_first_child().expect("enum item is missing body") self.0.cast_first_match().expect("enum item is missing body")
} }
} }
@ -565,13 +622,13 @@ node! {
impl TermItem { impl TermItem {
/// The term described by the item. /// The term described by the item.
pub fn term(&self) -> Markup { pub fn term(&self) -> Markup {
self.0.cast_first_child().expect("term list item is missing term") self.0.cast_first_match().expect("term list item is missing term")
} }
/// The description of the term. /// The description of the term.
pub fn description(&self) -> Markup { pub fn description(&self) -> Markup {
self.0 self.0
.cast_last_child() .cast_last_match()
.expect("term list item is missing description") .expect("term list item is missing description")
} }
} }
@ -602,10 +659,7 @@ node! {
impl Atom { impl Atom {
/// Get the atom's text. /// Get the atom's text.
pub fn get(&self) -> &EcoString { pub fn get(&self) -> &EcoString {
match self.0.kind() { self.0.text()
SyntaxKind::Atom(v) => v,
_ => panic!("atom is of wrong kind"),
}
} }
} }
@ -617,7 +671,7 @@ node! {
impl Script { impl Script {
/// The base of the script. /// The base of the script.
pub fn base(&self) -> Expr { pub fn base(&self) -> Expr {
self.0.cast_first_child().expect("script node is missing base") self.0.cast_first_match().expect("script node is missing base")
} }
/// The subscript. /// The subscript.
@ -647,32 +701,20 @@ node! {
impl Frac { impl Frac {
/// The numerator. /// The numerator.
pub fn num(&self) -> Expr { pub fn num(&self) -> Expr {
self.0.cast_first_child().expect("fraction is missing numerator") self.0.cast_first_match().expect("fraction is missing numerator")
} }
/// The denominator. /// The denominator.
pub fn denom(&self) -> Expr { pub fn denom(&self) -> Expr {
self.0.cast_last_child().expect("fraction is missing denominator") self.0.cast_last_match().expect("fraction is missing denominator")
} }
} }
node! { node! {
/// An alignment point in a formula: `&`, `&&`. /// An alignment point in a formula: `&`.
AlignPoint AlignPoint
} }
impl AlignPoint {
/// The number of ampersands.
pub fn count(&self) -> NonZeroUsize {
self.0
.children()
.filter(|n| n.kind() == &SyntaxKind::Amp)
.count()
.try_into()
.expect("alignment point is missing ampersand sign")
}
}
node! { node! {
/// An identifier: `it`. /// An identifier: `it`.
Ident Ident
@ -680,18 +722,16 @@ node! {
impl Ident { impl Ident {
/// Get the identifier. /// Get the identifier.
pub fn get(&self) -> &EcoString { pub fn get(&self) -> &str {
match self.0.kind() { self.0.text().trim_start_matches('#')
SyntaxKind::Ident(id) => id,
_ => panic!("identifier is of wrong kind"),
}
} }
/// Take out the container identifier. /// Take out the container identifier.
pub fn take(self) -> EcoString { pub fn take(self) -> EcoString {
match self.0.take() { let text = self.0.into_text();
SyntaxKind::Ident(id) => id, match text.strip_prefix('#') {
_ => panic!("identifier is of wrong kind"), Some(text) => text.into(),
Option::None => text,
} }
} }
@ -727,10 +767,7 @@ node! {
impl Bool { impl Bool {
/// Get the value. /// Get the value.
pub fn get(&self) -> bool { pub fn get(&self) -> bool {
match self.0.kind() { self.0.text() == "true"
SyntaxKind::Bool(v) => *v,
_ => panic!("boolean is of wrong kind"),
}
} }
} }
@ -742,10 +779,7 @@ node! {
impl Int { impl Int {
/// Get the value. /// Get the value.
pub fn get(&self) -> i64 { pub fn get(&self) -> i64 {
match self.0.kind() { self.0.text().parse().expect("integer is invalid")
SyntaxKind::Int(v) => *v,
_ => panic!("integer is of wrong kind"),
}
} }
} }
@ -757,10 +791,7 @@ node! {
impl Float { impl Float {
/// Get the value. /// Get the value.
pub fn get(&self) -> f64 { pub fn get(&self) -> f64 {
match self.0.kind() { self.0.text().parse().expect("float is invalid")
SyntaxKind::Float(v) => *v,
_ => panic!("float is of wrong kind"),
}
} }
} }
@ -772,13 +803,47 @@ node! {
impl Numeric { impl Numeric {
/// Get the value and unit. /// Get the value and unit.
pub fn get(&self) -> (f64, Unit) { pub fn get(&self) -> (f64, Unit) {
match self.0.kind() { let text = self.0.text();
SyntaxKind::Numeric(v, unit) => (*v, *unit), let count = text
_ => panic!("numeric is of wrong kind"), .chars()
} .rev()
.take_while(|c| matches!(c, 'a'..='z' | '%'))
.count();
let split = text.len() - count;
let value = text[..split].parse().expect("number is invalid");
let unit = match &text[split..] {
"pt" => Unit::Length(AbsUnit::Pt),
"mm" => Unit::Length(AbsUnit::Mm),
"cm" => Unit::Length(AbsUnit::Cm),
"in" => Unit::Length(AbsUnit::In),
"deg" => Unit::Angle(AngleUnit::Deg),
"rad" => Unit::Angle(AngleUnit::Rad),
"em" => Unit::Em,
"fr" => Unit::Fr,
"%" => Unit::Percent,
_ => panic!("number has invalid suffix"),
};
(value, unit)
} }
} }
/// Unit of a numeric value.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Unit {
/// An absolute length unit.
Length(AbsUnit),
/// An angular unit.
Angle(AngleUnit),
/// Font-relative: `1em` is the same as the font size.
Em,
/// Fractions: `fr`.
Fr,
/// Percentage: `%`.
Percent,
}
node! { node! {
/// A quoted string: `"..."`. /// A quoted string: `"..."`.
Str Str
@ -786,11 +851,46 @@ node! {
impl Str { impl Str {
/// Get the value. /// Get the value.
pub fn get(&self) -> &EcoString { pub fn get(&self) -> EcoString {
match self.0.kind() { let text = self.0.text();
SyntaxKind::Str(v) => v, let unquoted = &text[1..text.len() - 1];
_ => panic!("string is of wrong kind"), if !unquoted.contains('\\') {
return unquoted.into();
} }
let mut out = EcoString::with_capacity(unquoted.len());
let mut s = Scanner::new(unquoted);
while let Some(c) = s.eat() {
if c != '\\' {
out.push(c);
continue;
}
let start = s.locate(-1);
match s.eat() {
Some('\\') => out.push('\\'),
Some('"') => out.push('"'),
Some('n') => out.push('\n'),
Some('r') => out.push('\r'),
Some('t') => out.push('\t'),
Some('u') if s.eat_if('{') => {
let sequence = s.eat_while(char::is_ascii_hexdigit);
s.eat_if('}');
match u32::from_str_radix(sequence, 16)
.ok()
.and_then(std::char::from_u32)
{
Some(c) => out.push(c),
Option::None => out.push_str(s.from(start)),
}
}
_ => out.push_str(s.from(start)),
}
}
out
} }
} }
@ -814,7 +914,7 @@ node! {
impl ContentBlock { impl ContentBlock {
/// The contained markup. /// The contained markup.
pub fn body(&self) -> Markup { pub fn body(&self) -> Markup {
self.0.cast_first_child().expect("content block is missing body") self.0.cast_first_match().expect("content block is missing body")
} }
} }
@ -827,7 +927,7 @@ impl Parenthesized {
/// The wrapped expression. /// The wrapped expression.
pub fn expr(&self) -> Expr { pub fn expr(&self) -> Expr {
self.0 self.0
.cast_first_child() .cast_first_match()
.expect("parenthesized expression is missing expression") .expect("parenthesized expression is missing expression")
} }
} }
@ -856,7 +956,7 @@ pub enum ArrayItem {
impl AstNode for ArrayItem { impl AstNode for ArrayItem {
fn from_untyped(node: &SyntaxNode) -> Option<Self> { fn from_untyped(node: &SyntaxNode) -> Option<Self> {
match node.kind() { match node.kind() {
SyntaxKind::Spread => node.cast_first_child().map(Self::Spread), SyntaxKind::Spread => node.cast_first_match().map(Self::Spread),
_ => node.cast().map(Self::Pos), _ => node.cast().map(Self::Pos),
} }
} }
@ -897,7 +997,7 @@ impl AstNode for DictItem {
match node.kind() { match node.kind() {
SyntaxKind::Named => node.cast().map(Self::Named), SyntaxKind::Named => node.cast().map(Self::Named),
SyntaxKind::Keyed => node.cast().map(Self::Keyed), SyntaxKind::Keyed => node.cast().map(Self::Keyed),
SyntaxKind::Spread => node.cast_first_child().map(Self::Spread), SyntaxKind::Spread => node.cast_first_match().map(Self::Spread),
_ => Option::None, _ => Option::None,
} }
} }
@ -919,12 +1019,12 @@ node! {
impl Named { impl Named {
/// The name: `thickness`. /// The name: `thickness`.
pub fn name(&self) -> Ident { pub fn name(&self) -> Ident {
self.0.cast_first_child().expect("named pair is missing name") self.0.cast_first_match().expect("named pair is missing name")
} }
/// The right-hand side of the pair: `3pt`. /// The right-hand side of the pair: `3pt`.
pub fn expr(&self) -> Expr { pub fn expr(&self) -> Expr {
self.0.cast_last_child().expect("named pair is missing expression") self.0.cast_last_match().expect("named pair is missing expression")
} }
} }
@ -935,19 +1035,16 @@ node! {
impl Keyed { impl Keyed {
/// The key: `"spacy key"`. /// The key: `"spacy key"`.
pub fn key(&self) -> EcoString { pub fn key(&self) -> Str {
self.0 self.0
.children() .children()
.find_map(|node| match node.kind() { .find_map(|node| node.cast::<Str>())
SyntaxKind::Str(key) => Some(key.clone()),
_ => Option::None,
})
.expect("keyed pair is missing key") .expect("keyed pair is missing key")
} }
/// The right-hand side of the pair: `true`. /// The right-hand side of the pair: `true`.
pub fn expr(&self) -> Expr { pub fn expr(&self) -> Expr {
self.0.cast_last_child().expect("keyed pair is missing expression") self.0.cast_last_match().expect("keyed pair is missing expression")
} }
} }
@ -967,7 +1064,7 @@ impl Unary {
/// The expression to operate on: `x`. /// The expression to operate on: `x`.
pub fn expr(&self) -> Expr { pub fn expr(&self) -> Expr {
self.0.cast_last_child().expect("unary operation is missing child") self.0.cast_last_match().expect("unary operation is missing child")
} }
} }
@ -984,7 +1081,7 @@ pub enum UnOp {
impl UnOp { impl UnOp {
/// Try to convert the token into a unary operation. /// Try to convert the token into a unary operation.
pub fn from_token(token: &SyntaxKind) -> Option<Self> { pub fn from_token(token: SyntaxKind) -> Option<Self> {
Some(match token { Some(match token {
SyntaxKind::Plus => Self::Pos, SyntaxKind::Plus => Self::Pos,
SyntaxKind::Minus => Self::Neg, SyntaxKind::Minus => Self::Neg,
@ -1036,14 +1133,14 @@ impl Binary {
/// The left-hand side of the operation: `a`. /// The left-hand side of the operation: `a`.
pub fn lhs(&self) -> Expr { pub fn lhs(&self) -> Expr {
self.0 self.0
.cast_first_child() .cast_first_match()
.expect("binary operation is missing left-hand side") .expect("binary operation is missing left-hand side")
} }
/// The right-hand side of the operation: `b`. /// The right-hand side of the operation: `b`.
pub fn rhs(&self) -> Expr { pub fn rhs(&self) -> Expr {
self.0 self.0
.cast_last_child() .cast_last_match()
.expect("binary operation is missing right-hand side") .expect("binary operation is missing right-hand side")
} }
} }
@ -1093,7 +1190,7 @@ pub enum BinOp {
impl BinOp { impl BinOp {
/// Try to convert the token into a binary operation. /// Try to convert the token into a binary operation.
pub fn from_token(token: &SyntaxKind) -> Option<Self> { pub fn from_token(token: SyntaxKind) -> Option<Self> {
Some(match token { Some(match token {
SyntaxKind::Plus => Self::Add, SyntaxKind::Plus => Self::Add,
SyntaxKind::Minus => Self::Sub, SyntaxKind::Minus => Self::Sub,
@ -1210,12 +1307,12 @@ node! {
impl FieldAccess { impl FieldAccess {
/// The expression to access the field on. /// The expression to access the field on.
pub fn target(&self) -> Expr { pub fn target(&self) -> Expr {
self.0.cast_first_child().expect("field access is missing object") self.0.cast_first_match().expect("field access is missing object")
} }
/// The name of the field. /// The name of the field.
pub fn field(&self) -> Ident { pub fn field(&self) -> Ident {
self.0.cast_last_child().expect("field access is missing name") self.0.cast_last_match().expect("field access is missing name")
} }
} }
@ -1227,13 +1324,13 @@ node! {
impl FuncCall { impl FuncCall {
/// The function to call. /// The function to call.
pub fn callee(&self) -> Expr { pub fn callee(&self) -> Expr {
self.0.cast_first_child().expect("function call is missing callee") self.0.cast_first_match().expect("function call is missing callee")
} }
/// The arguments to the function. /// The arguments to the function.
pub fn args(&self) -> Args { pub fn args(&self) -> Args {
self.0 self.0
.cast_last_child() .cast_last_match()
.expect("function call is missing argument list") .expect("function call is missing argument list")
} }
} }
@ -1246,18 +1343,18 @@ node! {
impl MethodCall { impl MethodCall {
/// The expression to call the method on. /// The expression to call the method on.
pub fn target(&self) -> Expr { pub fn target(&self) -> Expr {
self.0.cast_first_child().expect("method call is missing target") self.0.cast_first_match().expect("method call is missing target")
} }
/// The name of the method. /// The name of the method.
pub fn method(&self) -> Ident { pub fn method(&self) -> Ident {
self.0.cast_last_child().expect("method call is missing name") self.0.cast_last_match().expect("method call is missing name")
} }
/// The arguments to the method. /// The arguments to the method.
pub fn args(&self) -> Args { pub fn args(&self) -> Args {
self.0 self.0
.cast_last_child() .cast_last_match()
.expect("method call is missing argument list") .expect("method call is missing argument list")
} }
} }
@ -1289,7 +1386,7 @@ impl AstNode for Arg {
fn from_untyped(node: &SyntaxNode) -> Option<Self> { fn from_untyped(node: &SyntaxNode) -> Option<Self> {
match node.kind() { match node.kind() {
SyntaxKind::Named => node.cast().map(Self::Named), SyntaxKind::Named => node.cast().map(Self::Named),
SyntaxKind::Spread => node.cast_first_child().map(Self::Spread), SyntaxKind::Spread => node.cast_first_match().map(Self::Spread),
_ => node.cast().map(Self::Pos), _ => node.cast().map(Self::Pos),
} }
} }
@ -1320,7 +1417,7 @@ impl Closure {
pub fn params(&self) -> impl DoubleEndedIterator<Item = Param> + '_ { pub fn params(&self) -> impl DoubleEndedIterator<Item = Param> + '_ {
self.0 self.0
.children() .children()
.find(|x| x.kind() == &SyntaxKind::Params) .find(|x| x.kind() == SyntaxKind::Params)
.expect("closure is missing parameter list") .expect("closure is missing parameter list")
.children() .children()
.filter_map(SyntaxNode::cast) .filter_map(SyntaxNode::cast)
@ -1328,7 +1425,7 @@ impl Closure {
/// The body of the closure. /// The body of the closure.
pub fn body(&self) -> Expr { pub fn body(&self) -> Expr {
self.0.cast_last_child().expect("closure is missing body") self.0.cast_last_match().expect("closure is missing body")
} }
} }
@ -1346,9 +1443,9 @@ pub enum Param {
impl AstNode for Param { impl AstNode for Param {
fn from_untyped(node: &SyntaxNode) -> Option<Self> { fn from_untyped(node: &SyntaxNode) -> Option<Self> {
match node.kind() { match node.kind() {
SyntaxKind::Ident(_) => node.cast().map(Self::Pos), SyntaxKind::Ident => node.cast().map(Self::Pos),
SyntaxKind::Named => node.cast().map(Self::Named), SyntaxKind::Named => node.cast().map(Self::Named),
SyntaxKind::Spread => node.cast_first_child().map(Self::Sink), SyntaxKind::Spread => node.cast_first_match().map(Self::Sink),
_ => Option::None, _ => Option::None,
} }
} }
@ -1370,7 +1467,7 @@ node! {
impl LetBinding { impl LetBinding {
/// The binding to assign to. /// The binding to assign to.
pub fn binding(&self) -> Ident { pub fn binding(&self) -> Ident {
match self.0.cast_first_child() { match self.0.cast_first_match() {
Some(Expr::Ident(binding)) => binding, Some(Expr::Ident(binding)) => binding,
Some(Expr::Closure(closure)) => { Some(Expr::Closure(closure)) => {
closure.name().expect("let-bound closure is missing name") closure.name().expect("let-bound closure is missing name")
@ -1381,12 +1478,12 @@ impl LetBinding {
/// The expression the binding is initialized with. /// The expression the binding is initialized with.
pub fn init(&self) -> Option<Expr> { pub fn init(&self) -> Option<Expr> {
if self.0.cast_first_child::<Ident>().is_some() { if self.0.cast_first_match::<Ident>().is_some() {
// This is a normal binding like `let x = 1`. // This is a normal binding like `let x = 1`.
self.0.children().filter_map(SyntaxNode::cast).nth(1) self.0.children().filter_map(SyntaxNode::cast).nth(1)
} else { } else {
// This is a closure binding like `let f(x) = 1`. // This is a closure binding like `let f(x) = 1`.
self.0.cast_first_child() self.0.cast_first_match()
} }
} }
} }
@ -1399,19 +1496,19 @@ node! {
impl SetRule { impl SetRule {
/// The function to set style properties for. /// The function to set style properties for.
pub fn target(&self) -> Ident { pub fn target(&self) -> Ident {
self.0.cast_first_child().expect("set rule is missing target") self.0.cast_first_match().expect("set rule is missing target")
} }
/// The style properties to set. /// The style properties to set.
pub fn args(&self) -> Args { pub fn args(&self) -> Args {
self.0.cast_last_child().expect("set rule is missing argument list") self.0.cast_last_match().expect("set rule is missing argument list")
} }
/// A condition under which the set rule applies. /// A condition under which the set rule applies.
pub fn condition(&self) -> Option<Expr> { pub fn condition(&self) -> Option<Expr> {
self.0 self.0
.children() .children()
.skip_while(|child| child.kind() != &SyntaxKind::If) .skip_while(|child| child.kind() != SyntaxKind::If)
.find_map(SyntaxNode::cast) .find_map(SyntaxNode::cast)
} }
} }
@ -1427,13 +1524,13 @@ impl ShowRule {
self.0 self.0
.children() .children()
.rev() .rev()
.skip_while(|child| child.kind() != &SyntaxKind::Colon) .skip_while(|child| child.kind() != SyntaxKind::Colon)
.find_map(SyntaxNode::cast) .find_map(SyntaxNode::cast)
} }
/// The transformation recipe. /// The transformation recipe.
pub fn transform(&self) -> Expr { pub fn transform(&self) -> Expr {
self.0.cast_last_child().expect("show rule is missing transform") self.0.cast_last_match().expect("show rule is missing transform")
} }
} }
@ -1445,7 +1542,7 @@ node! {
impl Conditional { impl Conditional {
/// The condition which selects the body to evaluate. /// The condition which selects the body to evaluate.
pub fn condition(&self) -> Expr { pub fn condition(&self) -> Expr {
self.0.cast_first_child().expect("conditional is missing condition") self.0.cast_first_match().expect("conditional is missing condition")
} }
/// The expression to evaluate if the condition is true. /// The expression to evaluate if the condition is true.
@ -1471,12 +1568,12 @@ node! {
impl WhileLoop { impl WhileLoop {
/// The condition which selects whether to evaluate the body. /// The condition which selects whether to evaluate the body.
pub fn condition(&self) -> Expr { pub fn condition(&self) -> Expr {
self.0.cast_first_child().expect("while loop is missing condition") self.0.cast_first_match().expect("while loop is missing condition")
} }
/// The expression to evaluate while the condition is true. /// The expression to evaluate while the condition is true.
pub fn body(&self) -> Expr { pub fn body(&self) -> Expr {
self.0.cast_last_child().expect("while loop is missing body") self.0.cast_last_match().expect("while loop is missing body")
} }
} }
@ -1488,17 +1585,17 @@ node! {
impl ForLoop { impl ForLoop {
/// The pattern to assign to. /// The pattern to assign to.
pub fn pattern(&self) -> ForPattern { pub fn pattern(&self) -> ForPattern {
self.0.cast_first_child().expect("for loop is missing pattern") self.0.cast_first_match().expect("for loop is missing pattern")
} }
/// The expression to iterate over. /// The expression to iterate over.
pub fn iter(&self) -> Expr { pub fn iter(&self) -> Expr {
self.0.cast_first_child().expect("for loop is missing iterable") self.0.cast_first_match().expect("for loop is missing iterable")
} }
/// The expression to evaluate for each iteration. /// The expression to evaluate for each iteration.
pub fn body(&self) -> Expr { pub fn body(&self) -> Expr {
self.0.cast_last_child().expect("for loop is missing body") self.0.cast_last_match().expect("for loop is missing body")
} }
} }
@ -1521,7 +1618,7 @@ impl ForPattern {
/// The value part of the pattern. /// The value part of the pattern.
pub fn value(&self) -> Ident { pub fn value(&self) -> Ident {
self.0.cast_last_child().expect("for loop pattern is missing value") self.0.cast_last_match().expect("for loop pattern is missing value")
} }
} }
@ -1533,7 +1630,7 @@ node! {
impl ModuleImport { impl ModuleImport {
/// The module or path from which the items should be imported. /// The module or path from which the items should be imported.
pub fn source(&self) -> Expr { pub fn source(&self) -> Expr {
self.0.cast_last_child().expect("module import is missing source") self.0.cast_last_match().expect("module import is missing source")
} }
/// The items to be imported. /// The items to be imported.
@ -1566,7 +1663,7 @@ node! {
impl ModuleInclude { impl ModuleInclude {
/// The module or path from which the content should be included. /// The module or path from which the content should be included.
pub fn source(&self) -> Expr { pub fn source(&self) -> Expr {
self.0.cast_last_child().expect("module include is missing path") self.0.cast_last_match().expect("module include is missing path")
} }
} }
@ -1588,6 +1685,6 @@ node! {
impl FuncReturn { impl FuncReturn {
/// The expression to return. /// The expression to return.
pub fn body(&self) -> Option<Expr> { pub fn body(&self) -> Option<Expr> {
self.0.cast_last_child() self.0.cast_last_match()
} }
} }

View File

@ -1,14 +1,7 @@
use std::hash::{Hash, Hasher};
use std::num::NonZeroUsize;
use std::sync::Arc;
use crate::geom::{AbsUnit, AngleUnit};
use crate::util::EcoString;
/// All syntactical building blocks that can be part of a Typst document. /// All syntactical building blocks that can be part of a Typst document.
/// ///
/// Can be created by the lexer or by the parser. /// Can be created by the lexer or by the parser.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum SyntaxKind { pub enum SyntaxKind {
/// A line comment: `// ...`. /// A line comment: `// ...`.
LineComment, LineComment,
@ -58,8 +51,6 @@ pub enum SyntaxKind {
Slash, Slash,
/// The superscript operator in a formula: `^`. /// The superscript operator in a formula: `^`.
Hat, Hat,
/// The alignment operator in a formula: `&`.
Amp,
/// The field access and method call operator: `.`. /// The field access and method call operator: `.`.
Dot, Dot,
/// The assignment operator: `=`. /// The assignment operator: `=`.
@ -135,31 +126,31 @@ pub enum SyntaxKind {
/// so it is zero except inside indent-aware constructs like lists. /// so it is zero except inside indent-aware constructs like lists.
Markup { min_indent: usize }, Markup { min_indent: usize },
/// Plain text without markup. /// Plain text without markup.
Text(EcoString), Text,
/// A forced line break: `\`. /// A forced line break: `\`.
Linebreak, Linebreak,
/// An escape sequence: `\#`, `\u{1F5FA}`. /// An escape sequence: `\#`, `\u{1F5FA}`.
Escape(char), Escape,
/// A shorthand for a unicode codepoint. For example, `~` for non-breaking /// A shorthand for a unicode codepoint. For example, `~` for non-breaking
/// space or `-?` for a soft hyphen. /// space or `-?` for a soft hyphen.
Shorthand(char), Shorthand,
/// Symbol notation: `:arrow:l:`. The string only contains the inner part /// Symbol notation: `:arrow:l:`. The string only contains the inner part
/// without leading and trailing dot. /// without leading and trailing dot.
Symbol(EcoString), Symbol,
/// A smart quote: `'` or `"`. /// A smart quote: `'` or `"`.
SmartQuote { double: bool }, SmartQuote,
/// Strong content: `*Strong*`. /// Strong content: `*Strong*`.
Strong, Strong,
/// Emphasized content: `_Emphasized_`. /// Emphasized content: `_Emphasized_`.
Emph, Emph,
/// Raw text with optional syntax highlighting: `` `...` ``. /// Raw text with optional syntax highlighting: `` `...` ``.
Raw(Arc<RawFields>), Raw { column: usize },
/// A hyperlink: `https://typst.org`. /// A hyperlink: `https://typst.org`.
Link(EcoString), Link,
/// A label: `<intro>`. /// A label: `<intro>`.
Label(EcoString), Label,
/// A reference: `@target`. /// A reference: `@target`.
Ref(EcoString), Ref,
/// A section heading: `= Introduction`. /// A section heading: `= Introduction`.
Heading, Heading,
/// An item in a bullet list: `- ...`. /// An item in a bullet list: `- ...`.
@ -167,32 +158,32 @@ pub enum SyntaxKind {
/// An item in an enumeration (numbered list): `+ ...` or `1. ...`. /// An item in an enumeration (numbered list): `+ ...` or `1. ...`.
EnumItem, EnumItem,
/// An explicit enumeration numbering: `23.`. /// An explicit enumeration numbering: `23.`.
EnumNumbering(NonZeroUsize), EnumNumbering,
/// An item in a term list: `/ Term: Details`. /// An item in a term list: `/ Term: Details`.
TermItem, TermItem,
/// A mathematical formula: `$x$`, `$ x^2 $`. /// A mathematical formula: `$x$`, `$ x^2 $`.
Math, Math,
/// An atom in a formula: `x`, `+`, `12`. /// An atom in a formula: `x`, `+`, `12`.
Atom(EcoString), Atom,
/// A base with optional sub- and superscripts in a formula: `a_1^2`. /// A base with optional sub- and superscripts in a formula: `a_1^2`.
Script, Script,
/// A fraction in a formula: `x/2`. /// A fraction in a formula: `x/2`.
Frac, Frac,
/// An alignment point in a formula: `&`, `&&`. /// An alignment point in a formula: `&`.
AlignPoint, AlignPoint,
/// An identifier: `it`. /// An identifier: `it`.
Ident(EcoString), Ident,
/// A boolean: `true`, `false`. /// A boolean: `true`, `false`.
Bool(bool), Bool,
/// An integer: `120`. /// An integer: `120`.
Int(i64), Int,
/// A floating-point number: `1.2`, `10e-4`. /// A floating-point number: `1.2`, `10e-4`.
Float(f64), Float,
/// A numeric value with a unit: `12pt`, `3cm`, `2em`, `90deg`, `50%`. /// A numeric value with a unit: `12pt`, `3cm`, `2em`, `90deg`, `50%`.
Numeric(f64, Unit), Numeric,
/// A quoted string: `"..."`. /// A quoted string: `"..."`.
Str(EcoString), Str,
/// A code block: `{ let x = 1; x + 2 }`. /// A code block: `{ let x = 1; x + 2 }`.
CodeBlock, CodeBlock,
/// A content block: `[*Hi* there!]`. /// A content block: `[*Hi* there!]`.
@ -253,73 +244,37 @@ pub enum SyntaxKind {
FuncReturn, FuncReturn,
/// An invalid sequence of characters. /// An invalid sequence of characters.
Error(ErrorPos, EcoString), Error,
}
/// Fields of the raw syntax kind.
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct RawFields {
/// An optional identifier specifying the language to syntax-highlight in.
pub lang: Option<EcoString>,
/// The raw text, determined as the raw string between the backticks trimmed
/// according to the above rules.
pub text: EcoString,
/// Whether the element is block-level, that is, it has 3+ backticks
/// and contains at least one newline.
pub block: bool,
}
/// Unit of a numeric value.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Unit {
/// An absolute length unit.
Length(AbsUnit),
/// An angular unit.
Angle(AngleUnit),
/// Font-relative: `1em` is the same as the font size.
Em,
/// Fractions: `fr`.
Fr,
/// Percentage: `%`.
Percent,
}
/// Where in a node an error should be annotated,
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum ErrorPos {
/// Over the full width of the node.
Full,
/// At the start of the node.
Start,
/// At the end of the node.
End,
} }
impl SyntaxKind { impl SyntaxKind {
/// Whether this is trivia. /// Whether this is trivia.
pub fn is_trivia(&self) -> bool { pub fn is_trivia(self) -> bool {
self.is_space() self.is_space() || self.is_comment() || self.is_error()
|| self.is_error()
|| matches!(self, Self::LineComment | Self::BlockComment)
} }
/// Whether this is a space. /// Whether this is a space.
pub fn is_space(&self) -> bool { pub fn is_space(self) -> bool {
matches!(self, Self::Space { .. }) matches!(self, Self::Space { .. })
} }
/// Whether this is a left or right parenthesis. /// Whether this is a comment.
pub fn is_paren(&self) -> bool { pub fn is_comment(self) -> bool {
matches!(self, Self::LeftParen | Self::RightParen) matches!(self, Self::LineComment | Self::BlockComment)
} }
/// Whether this is an error. /// Whether this is an error.
pub fn is_error(&self) -> bool { pub fn is_error(self) -> bool {
matches!(self, SyntaxKind::Error(_, _)) matches!(self, SyntaxKind::Error)
}
/// Whether this is a left or right parenthesis.
pub fn is_paren(self) -> bool {
matches!(self, Self::LeftParen | Self::RightParen)
} }
/// Does this node need termination through a semicolon or linebreak? /// Does this node need termination through a semicolon or linebreak?
pub fn is_stmt(&self) -> bool { pub fn is_stmt(self) -> bool {
matches!( matches!(
self, self,
SyntaxKind::LetBinding SyntaxKind::LetBinding
@ -331,7 +286,7 @@ impl SyntaxKind {
} }
/// A human-readable name for the kind. /// A human-readable name for the kind.
pub fn name(&self) -> &'static str { pub fn name(self) -> &'static str {
match self { match self {
Self::LineComment => "line comment", Self::LineComment => "line comment",
Self::BlockComment => "block comment", Self::BlockComment => "block comment",
@ -348,13 +303,11 @@ impl SyntaxKind {
Self::Star => "star", Self::Star => "star",
Self::Underscore => "underscore", Self::Underscore => "underscore",
Self::Dollar => "dollar sign", Self::Dollar => "dollar sign",
Self::SmartQuote { double: false } => "single quote", Self::SmartQuote => "smart quote",
Self::SmartQuote { double: true } => "double quote",
Self::Plus => "plus", Self::Plus => "plus",
Self::Minus => "minus", Self::Minus => "minus",
Self::Slash => "slash", Self::Slash => "slash",
Self::Hat => "hat", Self::Hat => "hat",
Self::Amp => "ampersand",
Self::Dot => "dot", Self::Dot => "dot",
Self::Eq => "assignment operator", Self::Eq => "assignment operator",
Self::EqEq => "equality operator", Self::EqEq => "equality operator",
@ -389,41 +342,33 @@ impl SyntaxKind {
Self::Include => "keyword `include`", Self::Include => "keyword `include`",
Self::As => "keyword `as`", Self::As => "keyword `as`",
Self::Markup { .. } => "markup", Self::Markup { .. } => "markup",
Self::Text(_) => "text", Self::Text => "text",
Self::Linebreak => "linebreak", Self::Linebreak => "linebreak",
Self::Escape(_) => "escape sequence", Self::Escape => "escape sequence",
Self::Shorthand(_) => "shorthand", Self::Shorthand => "shorthand",
Self::Symbol(_) => "symbol notation", Self::Symbol => "symbol notation",
Self::Strong => "strong content", Self::Strong => "strong content",
Self::Emph => "emphasized content", Self::Emph => "emphasized content",
Self::Raw(_) => "raw block", Self::Raw { .. } => "raw block",
Self::Link(_) => "link", Self::Link => "link",
Self::Label(_) => "label", Self::Label => "label",
Self::Ref(_) => "reference", Self::Ref => "reference",
Self::Heading => "heading", Self::Heading => "heading",
Self::ListItem => "list item", Self::ListItem => "list item",
Self::EnumItem => "enumeration item", Self::EnumItem => "enumeration item",
Self::EnumNumbering(_) => "enumeration item numbering", Self::EnumNumbering => "enumeration item numbering",
Self::TermItem => "term list item", Self::TermItem => "term list item",
Self::Math => "math formula", Self::Math => "math formula",
Self::Atom(s) => match s.as_str() { Self::Atom => "math atom",
"(" => "opening paren",
")" => "closing paren",
"{" => "opening brace",
"}" => "closing brace",
"[" => "opening bracket",
"]" => "closing bracket",
_ => "math atom",
},
Self::Script => "script", Self::Script => "script",
Self::Frac => "fraction", Self::Frac => "fraction",
Self::AlignPoint => "alignment point", Self::AlignPoint => "alignment point",
Self::Ident(_) => "identifier", Self::Ident => "identifier",
Self::Bool(_) => "boolean", Self::Bool => "boolean",
Self::Int(_) => "integer", Self::Int => "integer",
Self::Float(_) => "float", Self::Float => "float",
Self::Numeric(_, _) => "numeric value", Self::Numeric => "numeric value",
Self::Str(_) => "string", Self::Str => "string",
Self::CodeBlock => "code block", Self::CodeBlock => "code block",
Self::ContentBlock => "content block", Self::ContentBlock => "content block",
Self::Parenthesized => "group", Self::Parenthesized => "group",
@ -453,127 +398,7 @@ impl SyntaxKind {
Self::LoopBreak => "`break` expression", Self::LoopBreak => "`break` expression",
Self::LoopContinue => "`continue` expression", Self::LoopContinue => "`continue` expression",
Self::FuncReturn => "`return` expression", Self::FuncReturn => "`return` expression",
Self::Error(_, _) => "syntax error", Self::Error => "syntax error",
}
}
}
impl Hash for SyntaxKind {
fn hash<H: Hasher>(&self, state: &mut H) {
std::mem::discriminant(self).hash(state);
match self {
Self::LineComment => {}
Self::BlockComment => {}
Self::Space { newlines } => newlines.hash(state),
Self::LeftBrace => {}
Self::RightBrace => {}
Self::LeftBracket => {}
Self::RightBracket => {}
Self::LeftParen => {}
Self::RightParen => {}
Self::Comma => {}
Self::Semicolon => {}
Self::Colon => {}
Self::Star => {}
Self::Underscore => {}
Self::Dollar => {}
Self::Plus => {}
Self::Minus => {}
Self::Slash => {}
Self::Hat => {}
Self::Amp => {}
Self::Dot => {}
Self::Eq => {}
Self::EqEq => {}
Self::ExclEq => {}
Self::Lt => {}
Self::LtEq => {}
Self::Gt => {}
Self::GtEq => {}
Self::PlusEq => {}
Self::HyphEq => {}
Self::StarEq => {}
Self::SlashEq => {}
Self::Dots => {}
Self::Arrow => {}
Self::Not => {}
Self::And => {}
Self::Or => {}
Self::None => {}
Self::Auto => {}
Self::Let => {}
Self::Set => {}
Self::Show => {}
Self::If => {}
Self::Else => {}
Self::For => {}
Self::In => {}
Self::While => {}
Self::Break => {}
Self::Continue => {}
Self::Return => {}
Self::Import => {}
Self::Include => {}
Self::As => {}
Self::Markup { min_indent } => min_indent.hash(state),
Self::Text(s) => s.hash(state),
Self::Linebreak => {}
Self::Escape(c) => c.hash(state),
Self::Shorthand(c) => c.hash(state),
Self::Symbol(s) => s.hash(state),
Self::SmartQuote { double } => double.hash(state),
Self::Strong => {}
Self::Emph => {}
Self::Raw(raw) => raw.hash(state),
Self::Link(link) => link.hash(state),
Self::Label(c) => c.hash(state),
Self::Ref(c) => c.hash(state),
Self::Heading => {}
Self::ListItem => {}
Self::EnumItem => {}
Self::EnumNumbering(num) => num.hash(state),
Self::TermItem => {}
Self::Math => {}
Self::Atom(c) => c.hash(state),
Self::Script => {}
Self::Frac => {}
Self::AlignPoint => {}
Self::Ident(v) => v.hash(state),
Self::Bool(v) => v.hash(state),
Self::Int(v) => v.hash(state),
Self::Float(v) => v.to_bits().hash(state),
Self::Numeric(v, u) => (v.to_bits(), u).hash(state),
Self::Str(v) => v.hash(state),
Self::CodeBlock => {}
Self::ContentBlock => {}
Self::Parenthesized => {}
Self::Array => {}
Self::Dict => {}
Self::Named => {}
Self::Keyed => {}
Self::Unary => {}
Self::Binary => {}
Self::FieldAccess => {}
Self::FuncCall => {}
Self::MethodCall => {}
Self::Args => {}
Self::Spread => {}
Self::Closure => {}
Self::Params => {}
Self::LetBinding => {}
Self::SetRule => {}
Self::ShowRule => {}
Self::Conditional => {}
Self::WhileLoop => {}
Self::ForLoop => {}
Self::ForPattern => {}
Self::ModuleImport => {}
Self::ImportItems => {}
Self::ModuleInclude => {}
Self::LoopBreak => {}
Self::LoopContinue => {}
Self::FuncReturn => {}
Self::Error(pos, msg) => (pos, msg).hash(state),
} }
} }
} }

View File

@ -1,17 +1,12 @@
use std::num::NonZeroUsize;
use std::sync::Arc;
use unicode_xid::UnicodeXID; use unicode_xid::UnicodeXID;
use unscanny::Scanner; use unscanny::Scanner;
use super::resolve::{resolve_hex, resolve_raw, resolve_string}; use super::{ErrorPos, SyntaxKind};
use super::{ErrorPos, RawFields, SyntaxKind, Unit};
use crate::geom::{AbsUnit, AngleUnit};
use crate::util::{format_eco, EcoString}; use crate::util::{format_eco, EcoString};
/// Splits up a string of source code into tokens. /// Splits up a string of source code into tokens.
#[derive(Clone)] #[derive(Clone)]
pub struct Lexer<'s> { pub(super) struct Lexer<'s> {
/// The underlying scanner. /// The underlying scanner.
s: Scanner<'s>, s: Scanner<'s>,
/// The mode the lexer is in. This determines what tokens it recognizes. /// The mode the lexer is in. This determines what tokens it recognizes.
@ -20,11 +15,13 @@ pub struct Lexer<'s> {
terminated: bool, terminated: bool,
/// Offsets the indentation on the first line of the source. /// Offsets the indentation on the first line of the source.
column_offset: usize, column_offset: usize,
/// An error for the last token.
error: Option<(EcoString, ErrorPos)>,
} }
/// What kind of tokens to emit. /// What kind of tokens to emit.
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum LexMode { pub(super) enum LexMode {
/// Text and markup. /// Text and markup.
Markup, Markup,
/// Math atoms, operators, etc. /// Math atoms, operators, etc.
@ -34,11 +31,6 @@ pub enum LexMode {
} }
impl<'s> Lexer<'s> { impl<'s> Lexer<'s> {
/// Create a new lexer with the given mode.
pub fn new(text: &'s str, mode: LexMode) -> Self {
Self::with_prefix("", text, mode)
}
/// Create a new lexer with the given mode and a prefix to offset column /// Create a new lexer with the given mode and a prefix to offset column
/// calculations. /// calculations.
pub fn with_prefix(prefix: &str, text: &'s str, mode: LexMode) -> Self { pub fn with_prefix(prefix: &str, text: &'s str, mode: LexMode) -> Self {
@ -47,6 +39,7 @@ impl<'s> Lexer<'s> {
mode, mode,
terminated: true, terminated: true,
column_offset: column(prefix, prefix.len(), 0), column_offset: column(prefix, prefix.len(), 0),
error: None,
} }
} }
@ -85,6 +78,23 @@ impl<'s> Lexer<'s> {
pub fn column(&self, index: usize) -> usize { pub fn column(&self, index: usize) -> usize {
column(self.s.string(), index, self.column_offset) column(self.s.string(), index, self.column_offset)
} }
/// Take out the last error.
pub fn last_error(&mut self) -> Option<(EcoString, ErrorPos)> {
self.error.take()
}
/// Construct a full-positioned syntax error.
fn error(&mut self, message: impl Into<EcoString>) -> SyntaxKind {
self.error = Some((message.into(), ErrorPos::Full));
SyntaxKind::Error
}
/// Construct a positioned syntax error.
fn error_at_end(&mut self, message: impl Into<EcoString>) -> SyntaxKind {
self.error = Some((message.into(), ErrorPos::End));
SyntaxKind::Error
}
} }
impl Iterator for Lexer<'_> { impl Iterator for Lexer<'_> {
@ -92,22 +102,20 @@ impl Iterator for Lexer<'_> {
/// Produce the next token. /// Produce the next token.
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
self.error = None;
let start = self.s.cursor(); let start = self.s.cursor();
let c = self.s.eat()?; let c = self.s.eat()?;
Some(match c { Some(match c {
// Trivia. // Trivia.
c if c.is_whitespace() => self.whitespace(c),
'/' if self.s.eat_if('/') => self.line_comment(), '/' if self.s.eat_if('/') => self.line_comment(),
'/' if self.s.eat_if('*') => self.block_comment(), '/' if self.s.eat_if('*') => self.block_comment(),
'*' if self.s.eat_if('/') => SyntaxKind::Error( '*' if self.s.eat_if('/') => self.error("unexpected end of block comment"),
ErrorPos::Full,
"unexpected end of block comment".into(),
),
c if c.is_whitespace() => self.whitespace(c),
// Other things. // Other things.
_ => match self.mode { _ => match self.mode {
LexMode::Markup => self.markup(start, c), LexMode::Markup => self.markup(start, c),
LexMode::Math => self.math(start, c), LexMode::Math => self.math(c),
LexMode::Code => self.code(start, c), LexMode::Code => self.code(start, c),
}, },
}) })
@ -118,7 +126,7 @@ impl Iterator for Lexer<'_> {
impl Lexer<'_> { impl Lexer<'_> {
fn line_comment(&mut self) -> SyntaxKind { fn line_comment(&mut self) -> SyntaxKind {
self.s.eat_until(is_newline); self.s.eat_until(is_newline);
if self.s.peek().is_none() { if self.s.done() {
self.terminated = false; self.terminated = false;
} }
SyntaxKind::LineComment SyntaxKind::LineComment
@ -182,57 +190,64 @@ impl Lexer<'_> {
} }
} }
/// Markup.
impl Lexer<'_> { impl Lexer<'_> {
fn markup(&mut self, start: usize, c: char) -> SyntaxKind { fn markup(&mut self, start: usize, c: char) -> SyntaxKind {
match c { match c {
// Blocks. '\\' => self.backslash(),
':' if self.s.at(is_id_start) => self.maybe_symbol(),
'`' => self.raw(),
'h' if self.s.eat_if("ttp://") => self.link(),
'h' if self.s.eat_if("ttps://") => self.link(),
'<' if self.s.at(is_id_continue) => self.label(),
'@' if self.s.at(is_id_continue) => self.reference(),
'0'..='9' => self.numbering(start),
'#' if self.s.eat_if('{') => SyntaxKind::LeftBrace,
'#' if self.s.eat_if('[') => SyntaxKind::LeftBracket,
'#' if self.s.at(is_id_start) => {
match keyword(self.s.eat_while(is_id_continue)) {
Some(keyword) => keyword,
None => SyntaxKind::Ident,
}
}
'.' if self.s.eat_if("..") => SyntaxKind::Shorthand,
'-' if self.s.eat_if("--") => SyntaxKind::Shorthand,
'-' if self.s.eat_if('-') => SyntaxKind::Shorthand,
'-' if self.s.eat_if('?') => SyntaxKind::Shorthand,
'*' if !self.in_word() => SyntaxKind::Star,
'_' if !self.in_word() => SyntaxKind::Underscore,
'{' => SyntaxKind::LeftBrace, '{' => SyntaxKind::LeftBrace,
'}' => SyntaxKind::RightBrace, '}' => SyntaxKind::RightBrace,
'[' => SyntaxKind::LeftBracket, '[' => SyntaxKind::LeftBracket,
']' => SyntaxKind::RightBracket, ']' => SyntaxKind::RightBracket,
'\'' => SyntaxKind::SmartQuote,
// Multi-char things. '"' => SyntaxKind::SmartQuote,
'#' => self.hash(start),
'.' if self.s.eat_if("..") => SyntaxKind::Shorthand('\u{2026}'),
'-' => self.hyph(),
':' => self.colon(),
'h' if self.s.eat_if("ttp://") || self.s.eat_if("ttps://") => {
self.link(start)
}
'`' => self.raw(),
c if c.is_ascii_digit() => self.numbering(start),
'<' if self.s.at(is_id_continue) => self.label(),
'@' if self.s.at(is_id_continue) => self.reference(),
// Escape sequences.
'\\' => self.backslash(),
// Single-char things.
'~' => SyntaxKind::Shorthand('\u{00A0}'),
'\'' => SyntaxKind::SmartQuote { double: false },
'"' => SyntaxKind::SmartQuote { double: true },
'*' if !self.in_word() => SyntaxKind::Star,
'_' if !self.in_word() => SyntaxKind::Underscore,
'$' => SyntaxKind::Dollar, '$' => SyntaxKind::Dollar,
'=' => SyntaxKind::Eq, '=' => SyntaxKind::Eq,
'+' => SyntaxKind::Plus, '+' => SyntaxKind::Plus,
'/' => SyntaxKind::Slash, '/' => SyntaxKind::Slash,
'~' => SyntaxKind::Shorthand,
':' => SyntaxKind::Colon,
'-' => SyntaxKind::Minus,
// Plain text. _ => self.text(),
_ => self.text(start),
} }
} }
fn text(&mut self, start: usize) -> SyntaxKind { fn text(&mut self) -> SyntaxKind {
macro_rules! table { macro_rules! table {
($(|$c:literal)*) => {{ ($(|$c:literal)*) => {
let mut t = [false; 128]; static TABLE: [bool; 128] = {
$(t[$c as usize] = true;)* let mut t = [false; 128];
t $(t[$c as usize] = true;)*
}} t
};
};
} }
const TABLE: [bool; 128] = table! { table! {
| ' ' | '\t' | '\n' | '\x0b' | '\x0c' | '\r' | '\\' | '/' | ' ' | '\t' | '\n' | '\x0b' | '\x0c' | '\r' | '\\' | '/'
| '[' | ']' | '{' | '}' | '~' | '-' | '.' | '\'' | '"' | '[' | ']' | '{' | '}' | '~' | '-' | '.' | '\'' | '"'
| '*' | '_' | ':' | 'h' | '`' | '$' | '<' | '>' | '@' | '#' | '*' | '_' | ':' | 'h' | '`' | '$' | '<' | '>' | '@' | '#'
@ -247,8 +262,8 @@ impl Lexer<'_> {
// anyway. // anyway.
let mut s = self.s; let mut s = self.s;
match s.eat() { match s.eat() {
Some('/') if !s.at(['/', '*']) => {}
Some(' ') if s.at(char::is_alphanumeric) => {} Some(' ') if s.at(char::is_alphanumeric) => {}
Some('/') if !s.at(['/', '*']) => {}
Some('-') if !s.at(['-', '?']) => {} Some('-') if !s.at(['-', '?']) => {}
Some('.') if !s.at("..") => {} Some('.') if !s.at("..") => {}
Some('h') if !s.at("ttp://") && !s.at("ttps://") => {} Some('h') if !s.at("ttp://") && !s.at("ttps://") => {}
@ -259,77 +274,40 @@ impl Lexer<'_> {
self.s = s; self.s = s;
} }
SyntaxKind::Text(self.s.from(start).into()) SyntaxKind::Text
} }
fn backslash(&mut self) -> SyntaxKind { fn backslash(&mut self) -> SyntaxKind {
match self.s.peek() { if self.s.eat_if("u{") {
Some('u') if self.s.eat_if("u{") => { let hex = self.s.eat_while(char::is_ascii_alphanumeric);
let sequence = self.s.eat_while(char::is_ascii_alphanumeric); if !self.s.eat_if('}') {
if self.s.eat_if('}') { self.terminated = false;
if let Some(c) = resolve_hex(sequence) { return self.error_at_end("expected closing brace");
SyntaxKind::Escape(c)
} else {
SyntaxKind::Error(
ErrorPos::Full,
"invalid unicode escape sequence".into(),
)
}
} else {
self.terminated = false;
SyntaxKind::Error(ErrorPos::End, "expected closing brace".into())
}
} }
// Linebreaks. if u32::from_str_radix(hex, 16)
Some(c) if c.is_whitespace() => SyntaxKind::Linebreak, .ok()
None => SyntaxKind::Linebreak, .and_then(std::char::from_u32)
.is_none()
// Escapes. {
Some(c) => { return self.error("invalid unicode escape sequence");
self.s.expect(c);
SyntaxKind::Escape(c)
} }
return SyntaxKind::Escape;
} }
}
fn hash(&mut self, start: usize) -> SyntaxKind { if self.s.done() || self.s.at(char::is_whitespace) {
if self.s.eat_if('{') { SyntaxKind::Linebreak
SyntaxKind::LeftBrace
} else if self.s.eat_if('[') {
SyntaxKind::LeftBracket
} else if self.s.at(is_id_start) {
let read = self.s.eat_while(is_id_continue);
match keyword(read) {
Some(keyword) => keyword,
None => SyntaxKind::Ident(read.into()),
}
} else if self.mode == LexMode::Markup {
self.text(start)
} else { } else {
SyntaxKind::Atom("#".into()) self.s.eat();
SyntaxKind::Escape
} }
} }
fn hyph(&mut self) -> SyntaxKind { fn maybe_symbol(&mut self) -> SyntaxKind {
if self.s.eat_if('-') {
if self.s.eat_if('-') {
SyntaxKind::Shorthand('\u{2014}')
} else {
SyntaxKind::Shorthand('\u{2013}')
}
} else if self.s.eat_if('?') {
SyntaxKind::Shorthand('\u{00AD}')
} else {
SyntaxKind::Minus
}
}
fn colon(&mut self) -> SyntaxKind {
let start = self.s.cursor(); let start = self.s.cursor();
let mut end = start; let mut end = start;
while !self.s.eat_while(char::is_ascii_alphanumeric).is_empty() && self.s.at(':') while !self.s.eat_while(is_id_continue).is_empty() && self.s.at(':') {
{
end = self.s.cursor(); end = self.s.cursor();
self.s.eat(); self.s.eat();
} }
@ -338,15 +316,15 @@ impl Lexer<'_> {
if start < end { if start < end {
self.s.expect(':'); self.s.expect(':');
SyntaxKind::Symbol(self.s.get(start..end).into()) SyntaxKind::Symbol
} else if self.mode == LexMode::Markup { } else if self.mode == LexMode::Markup {
SyntaxKind::Colon SyntaxKind::Colon
} else { } else {
SyntaxKind::Atom(":".into()) SyntaxKind::Atom
} }
} }
fn link(&mut self, start: usize) -> SyntaxKind { fn link(&mut self) -> SyntaxKind {
#[rustfmt::skip] #[rustfmt::skip]
self.s.eat_while(|c: char| matches!(c, self.s.eat_while(|c: char| matches!(c,
| '0' ..= '9' | '0' ..= '9'
@ -355,10 +333,12 @@ impl Lexer<'_> {
| '~' | '/' | '%' | '?' | '#' | '&' | '+' | '=' | '~' | '/' | '%' | '?' | '#' | '&' | '+' | '='
| '\'' | '.' | ',' | ';' | '\'' | '.' | ',' | ';'
)); ));
if self.s.scout(-1) == Some('.') { if self.s.scout(-1) == Some('.') {
self.s.uneat(); self.s.uneat();
} }
SyntaxKind::Link(self.s.from(start).into())
SyntaxKind::Link
} }
fn raw(&mut self) -> SyntaxKind { fn raw(&mut self) -> SyntaxKind {
@ -369,16 +349,10 @@ impl Lexer<'_> {
backticks += 1; backticks += 1;
} }
// Special case for empty inline block.
if backticks == 2 { if backticks == 2 {
return SyntaxKind::Raw(Arc::new(RawFields { return SyntaxKind::Raw { column };
text: EcoString::new(),
lang: None,
block: false,
}));
} }
let start = self.s.cursor();
let mut found = 0; let mut found = 0;
while found < backticks { while found < backticks {
match self.s.eat() { match self.s.eat() {
@ -388,45 +362,40 @@ impl Lexer<'_> {
} }
} }
if found == backticks { if found != backticks {
let end = self.s.cursor() - found as usize;
SyntaxKind::Raw(Arc::new(resolve_raw(
column,
backticks,
self.s.get(start..end),
)))
} else {
self.terminated = false; self.terminated = false;
let remaining = backticks - found; let remaining = backticks - found;
let noun = if remaining == 1 { "backtick" } else { "backticks" }; let noun = if remaining == 1 { "backtick" } else { "backticks" };
SyntaxKind::Error( return self.error_at_end(if found == 0 {
ErrorPos::End, format_eco!("expected {} {}", remaining, noun)
if found == 0 { } else {
format_eco!("expected {} {}", remaining, noun) format_eco!("expected {} more {}", remaining, noun)
} else { });
format_eco!("expected {} more {}", remaining, noun)
},
)
} }
SyntaxKind::Raw { column }
} }
fn numbering(&mut self, start: usize) -> SyntaxKind { fn numbering(&mut self, start: usize) -> SyntaxKind {
self.s.eat_while(char::is_ascii_digit); self.s.eat_while(char::is_ascii_digit);
let read = self.s.from(start); let read = self.s.from(start);
if self.s.eat_if('.') { if self.s.eat_if('.') {
if let Ok(number) = read.parse::<usize>() { if let Ok(number) = read.parse::<usize>() {
return match NonZeroUsize::new(number) { if number == 0 {
Some(number) => SyntaxKind::EnumNumbering(number), return self.error("must be positive");
None => SyntaxKind::Error(ErrorPos::Full, "must be positive".into()), }
};
return SyntaxKind::EnumNumbering;
} }
} }
self.text(start) self.text()
} }
fn reference(&mut self) -> SyntaxKind { fn reference(&mut self) -> SyntaxKind {
SyntaxKind::Ref(self.s.eat_while(is_id_continue).into()) self.s.eat_while(is_id_continue);
SyntaxKind::Ref
} }
fn in_word(&self) -> bool { fn in_word(&self) -> bool {
@ -439,95 +408,83 @@ impl Lexer<'_> {
/// Math. /// Math.
impl Lexer<'_> { impl Lexer<'_> {
fn math(&mut self, start: usize, c: char) -> SyntaxKind { fn math(&mut self, c: char) -> SyntaxKind {
match c { match c {
// Symbol shorthands.
'|' if self.s.eat_if("->") => SyntaxKind::Shorthand('\u{21A6}'),
'<' if self.s.eat_if("->") => SyntaxKind::Shorthand('\u{2194}'),
'<' if self.s.eat_if("=>") => SyntaxKind::Shorthand('\u{21D4}'),
'!' if self.s.eat_if('=') => SyntaxKind::Shorthand('\u{2260}'),
'<' if self.s.eat_if('=') => SyntaxKind::Shorthand('\u{2264}'),
'>' if self.s.eat_if('=') => SyntaxKind::Shorthand('\u{2265}'),
'<' if self.s.eat_if('-') => SyntaxKind::Shorthand('\u{2190}'),
'-' if self.s.eat_if('>') => SyntaxKind::Shorthand('\u{2192}'),
'=' if self.s.eat_if('>') => SyntaxKind::Shorthand('\u{21D2}'),
':' if self.s.eat_if('=') => SyntaxKind::Shorthand('\u{2254}'),
// Multi-char things.
'#' => self.hash(start),
// Escape sequences.
'\\' => self.backslash(), '\\' => self.backslash(),
':' if self.s.at(is_id_start) => self.maybe_symbol(),
// Single-char things.
'_' => SyntaxKind::Underscore,
'^' => SyntaxKind::Hat,
'/' => SyntaxKind::Slash,
'&' => SyntaxKind::Amp,
'$' => SyntaxKind::Dollar,
// Symbol notation.
':' => self.colon(),
// Strings.
'"' => self.string(), '"' => self.string(),
'#' if self.s.eat_if('{') => SyntaxKind::LeftBrace,
'#' if self.s.eat_if('[') => SyntaxKind::LeftBracket,
'#' if self.s.at(is_id_start) => {
match keyword(self.s.eat_while(is_id_continue)) {
Some(keyword) => keyword,
None => SyntaxKind::Ident,
}
}
'|' if self.s.eat_if("->") => SyntaxKind::Shorthand,
'<' if self.s.eat_if("->") => SyntaxKind::Shorthand,
'<' if self.s.eat_if("=>") => SyntaxKind::Shorthand,
'!' if self.s.eat_if('=') => SyntaxKind::Shorthand,
'<' if self.s.eat_if('=') => SyntaxKind::Shorthand,
'>' if self.s.eat_if('=') => SyntaxKind::Shorthand,
'<' if self.s.eat_if('-') => SyntaxKind::Shorthand,
'-' if self.s.eat_if('>') => SyntaxKind::Shorthand,
'=' if self.s.eat_if('>') => SyntaxKind::Shorthand,
':' if self.s.eat_if('=') => SyntaxKind::Shorthand,
'_' => SyntaxKind::Underscore,
'$' => SyntaxKind::Dollar,
'/' => SyntaxKind::Slash,
'^' => SyntaxKind::Hat,
'&' => SyntaxKind::AlignPoint,
// Identifiers and symbol notation. // Identifiers and symbol notation.
c if is_math_id_start(c) && self.s.at(is_math_id_continue) => { c if is_math_id_start(c) && self.s.at(is_math_id_continue) => {
self.s.eat_while(is_math_id_continue); self.math_ident()
let mut symbol = false;
while self.s.eat_if(':')
&& !self.s.eat_while(char::is_alphanumeric).is_empty()
{
symbol = true;
}
if symbol {
SyntaxKind::Symbol(self.s.from(start).into())
} else {
if self.s.scout(-1) == Some(':') {
self.s.uneat();
}
SyntaxKind::Ident(self.s.from(start).into())
}
}
// Numbers.
c if c.is_numeric() => {
self.s.eat_while(char::is_numeric);
SyntaxKind::Atom(self.s.from(start).into())
} }
// Other math atoms. // Other math atoms.
c => SyntaxKind::Atom(c.into()), _ => {
// Keep numbers together.
if c.is_numeric() {
self.s.eat_while(char::is_numeric);
}
SyntaxKind::Atom
}
} }
} }
fn math_ident(&mut self) -> SyntaxKind {
self.s.eat_while(is_math_id_continue);
let mut symbol = false;
while self.s.eat_if(':') && !self.s.eat_while(char::is_alphanumeric).is_empty() {
symbol = true;
}
if symbol {
return SyntaxKind::Symbol;
}
if self.s.scout(-1) == Some(':') {
self.s.uneat();
}
SyntaxKind::Ident
}
} }
/// Code. /// Code.
impl Lexer<'_> { impl Lexer<'_> {
fn code(&mut self, start: usize, c: char) -> SyntaxKind { fn code(&mut self, start: usize, c: char) -> SyntaxKind {
match c { match c {
// Blocks.
'{' => SyntaxKind::LeftBrace,
'}' => SyntaxKind::RightBrace,
'[' => SyntaxKind::LeftBracket,
']' => SyntaxKind::RightBracket,
// Parentheses.
'(' => SyntaxKind::LeftParen,
')' => SyntaxKind::RightParen,
// Math.
'$' => SyntaxKind::Dollar,
// Labels and raw.
'<' if self.s.at(is_id_continue) => self.label(),
'`' => self.raw(), '`' => self.raw(),
'<' if self.s.at(is_id_continue) => self.label(),
'0'..='9' => self.number(start, c),
'.' if self.s.at(char::is_ascii_digit) => self.number(start, c),
'"' => self.string(),
// Two-char operators.
'=' if self.s.eat_if('=') => SyntaxKind::EqEq, '=' if self.s.eat_if('=') => SyntaxKind::EqEq,
'!' if self.s.eat_if('=') => SyntaxKind::ExclEq, '!' if self.s.eat_if('=') => SyntaxKind::ExclEq,
'<' if self.s.eat_if('=') => SyntaxKind::LtEq, '<' if self.s.eat_if('=') => SyntaxKind::LtEq,
@ -539,10 +496,17 @@ impl Lexer<'_> {
'.' if self.s.eat_if('.') => SyntaxKind::Dots, '.' if self.s.eat_if('.') => SyntaxKind::Dots,
'=' if self.s.eat_if('>') => SyntaxKind::Arrow, '=' if self.s.eat_if('>') => SyntaxKind::Arrow,
// Single-char operators. '{' => SyntaxKind::LeftBrace,
'}' => SyntaxKind::RightBrace,
'[' => SyntaxKind::LeftBracket,
']' => SyntaxKind::RightBracket,
'(' => SyntaxKind::LeftParen,
')' => SyntaxKind::RightParen,
'$' => SyntaxKind::Dollar,
',' => SyntaxKind::Comma, ',' => SyntaxKind::Comma,
';' => SyntaxKind::Semicolon, ';' => SyntaxKind::Semicolon,
':' => SyntaxKind::Colon, ':' => SyntaxKind::Colon,
'.' => SyntaxKind::Dot,
'+' => SyntaxKind::Plus, '+' => SyntaxKind::Plus,
'-' => SyntaxKind::Minus, '-' => SyntaxKind::Minus,
'*' => SyntaxKind::Star, '*' => SyntaxKind::Star,
@ -550,21 +514,10 @@ impl Lexer<'_> {
'=' => SyntaxKind::Eq, '=' => SyntaxKind::Eq,
'<' => SyntaxKind::Lt, '<' => SyntaxKind::Lt,
'>' => SyntaxKind::Gt, '>' => SyntaxKind::Gt,
'.' if !self.s.at(char::is_ascii_digit) => SyntaxKind::Dot,
// Identifiers.
c if is_id_start(c) => self.ident(start), c if is_id_start(c) => self.ident(start),
// Numbers. _ => self.error("not valid here"),
c if c.is_ascii_digit() || (c == '.' && self.s.at(char::is_ascii_digit)) => {
self.number(start, c)
}
// Strings.
'"' => self.string(),
// Invalid token.
_ => SyntaxKind::Error(ErrorPos::Full, "not valid here".into()),
} }
} }
@ -573,9 +526,9 @@ impl Lexer<'_> {
match self.s.from(start) { match self.s.from(start) {
"none" => SyntaxKind::None, "none" => SyntaxKind::None,
"auto" => SyntaxKind::Auto, "auto" => SyntaxKind::Auto,
"true" => SyntaxKind::Bool(true), "true" => SyntaxKind::Bool,
"false" => SyntaxKind::Bool(false), "false" => SyntaxKind::Bool,
id => keyword(id).unwrap_or_else(|| SyntaxKind::Ident(id.into())), id => keyword(id).unwrap_or(SyntaxKind::Ident),
} }
} }
@ -604,64 +557,54 @@ impl Lexer<'_> {
let number = self.s.get(start..suffix_start); let number = self.s.get(start..suffix_start);
let suffix = self.s.from(suffix_start); let suffix = self.s.from(suffix_start);
// Find out whether it is a simple number.
if suffix.is_empty() { if suffix.is_empty() {
if let Ok(i) = number.parse::<i64>() { return if number.parse::<i64>().is_ok() {
return SyntaxKind::Int(i); SyntaxKind::Int
} } else if number.parse::<f64>().is_ok() {
SyntaxKind::Float
} else {
self.error("invalid number")
};
} }
let Ok(v) = number.parse::<f64>() else { if !matches!(
return SyntaxKind::Error(ErrorPos::Full, "invalid number".into()); suffix,
}; "pt" | "mm" | "cm" | "in" | "deg" | "rad" | "em" | "fr" | "%"
) {
match suffix { return self.error("invalid number suffix");
"" => SyntaxKind::Float(v),
"pt" => SyntaxKind::Numeric(v, Unit::Length(AbsUnit::Pt)),
"mm" => SyntaxKind::Numeric(v, Unit::Length(AbsUnit::Mm)),
"cm" => SyntaxKind::Numeric(v, Unit::Length(AbsUnit::Cm)),
"in" => SyntaxKind::Numeric(v, Unit::Length(AbsUnit::In)),
"deg" => SyntaxKind::Numeric(v, Unit::Angle(AngleUnit::Deg)),
"rad" => SyntaxKind::Numeric(v, Unit::Angle(AngleUnit::Rad)),
"em" => SyntaxKind::Numeric(v, Unit::Em),
"fr" => SyntaxKind::Numeric(v, Unit::Fr),
"%" => SyntaxKind::Numeric(v, Unit::Percent),
_ => SyntaxKind::Error(ErrorPos::Full, "invalid number suffix".into()),
} }
SyntaxKind::Numeric
} }
fn string(&mut self) -> SyntaxKind { fn string(&mut self) -> SyntaxKind {
let mut escaped = false; let mut escaped = false;
let verbatim = self.s.eat_until(|c| { self.s.eat_until(|c| {
if c == '"' && !escaped { let stop = c == '"' && !escaped;
true escaped = c == '\\' && !escaped;
} else { stop
escaped = c == '\\' && !escaped;
false
}
}); });
let string = resolve_string(verbatim); if !self.s.eat_if('"') {
if self.s.eat_if('"') {
SyntaxKind::Str(string)
} else {
self.terminated = false; self.terminated = false;
SyntaxKind::Error(ErrorPos::End, "expected quote".into()) return self.error_at_end("expected quote");
} }
SyntaxKind::Str
} }
fn label(&mut self) -> SyntaxKind { fn label(&mut self) -> SyntaxKind {
let label = self.s.eat_while(is_id_continue); let label = self.s.eat_while(is_id_continue);
if self.s.eat_if('>') { if label.is_empty() {
if !label.is_empty() { return self.error("label cannot be empty");
SyntaxKind::Label(label.into())
} else {
SyntaxKind::Error(ErrorPos::Full, "label cannot be empty".into())
}
} else {
self.terminated = false;
SyntaxKind::Error(ErrorPos::End, "expected closing angle bracket".into())
} }
if !self.s.eat_if('>') {
self.terminated = false;
return self.error_at_end("expected closing angle bracket");
}
SyntaxKind::Label
} }
} }
@ -729,6 +672,29 @@ pub fn is_newline(character: char) -> bool {
) )
} }
/// Split text at newlines.
pub(super) fn split_newlines(text: &str) -> Vec<&str> {
let mut s = Scanner::new(text);
let mut lines = Vec::new();
let mut start = 0;
let mut end = 0;
while let Some(c) = s.eat() {
if is_newline(c) {
if c == '\r' {
s.eat_if('\n');
}
lines.push(&text[start..end]);
start = s.cursor();
}
end = s.cursor();
}
lines.push(&text[start..]);
lines
}
/// Whether a string is a valid unicode identifier. /// Whether a string is a valid unicode identifier.
/// ///
/// In addition to what is specified in the [Unicode Standard][uax31], we allow: /// In addition to what is specified in the [Unicode Standard][uax31], we allow:
@ -746,13 +712,13 @@ pub fn is_ident(string: &str) -> bool {
/// Whether a character can start an identifier. /// Whether a character can start an identifier.
#[inline] #[inline]
fn is_id_start(c: char) -> bool { pub(super) fn is_id_start(c: char) -> bool {
c.is_xid_start() || c == '_' c.is_xid_start() || c == '_'
} }
/// Whether a character can continue an identifier. /// Whether a character can continue an identifier.
#[inline] #[inline]
fn is_id_continue(c: char) -> bool { pub(super) fn is_id_continue(c: char) -> bool {
c.is_xid_continue() || c == '_' || c == '-' c.is_xid_continue() || c == '_' || c == '-'
} }

View File

View File

@ -2,22 +2,17 @@
pub mod ast; pub mod ast;
mod incremental;
mod kind; mod kind;
mod lexer; mod lexer;
mod node; mod node;
mod parser; mod parser;
mod parsing; mod reparse;
mod resolve;
mod source; mod source;
mod span; mod span;
pub use self::kind::*; pub use self::kind::*;
pub use self::lexer::*; pub use self::lexer::*;
pub use self::node::*; pub use self::node::*;
pub use self::parsing::*; pub use self::parser::*;
pub use self::source::*; pub use self::source::*;
pub use self::span::*; pub use self::span::*;
use incremental::reparse;
use parser::*;

View File

@ -6,6 +6,7 @@ use std::sync::Arc;
use super::ast::AstNode; use super::ast::AstNode;
use super::{SourceId, Span, SyntaxKind}; use super::{SourceId, Span, SyntaxKind};
use crate::diag::SourceError; use crate::diag::SourceError;
use crate::util::EcoString;
/// A node in the untyped syntax tree. /// A node in the untyped syntax tree.
#[derive(Clone, PartialEq, Hash)] #[derive(Clone, PartialEq, Hash)]
@ -15,84 +16,106 @@ pub struct SyntaxNode(Repr);
#[derive(Clone, PartialEq, Hash)] #[derive(Clone, PartialEq, Hash)]
enum Repr { enum Repr {
/// A leaf node. /// A leaf node.
Leaf(NodeData), Leaf(LeafNode),
/// A reference-counted inner node. /// A reference-counted inner node.
Inner(Arc<InnerNode>), Inner(Arc<InnerNode>),
/// An error.
Error(ErrorNode),
} }
impl SyntaxNode { impl SyntaxNode {
/// Create a new leaf node. /// Create a new leaf node.
pub fn leaf(kind: SyntaxKind, len: usize) -> Self { pub fn leaf(kind: SyntaxKind, text: impl Into<EcoString>) -> Self {
Self(Repr::Leaf(NodeData::new(kind, len))) Self(Repr::Leaf(LeafNode::new(kind, text)))
} }
/// Create a new inner node with children. /// Create a new inner node with children.
pub fn inner(kind: SyntaxKind, children: Vec<SyntaxNode>) -> Self { pub fn inner(kind: SyntaxKind, children: Vec<SyntaxNode>) -> Self {
Self(Repr::Inner(Arc::new(InnerNode::with_children(kind, children)))) Self(Repr::Inner(Arc::new(InnerNode::new(kind, children))))
}
/// Create a new error node.
pub fn error(message: impl Into<EcoString>, pos: ErrorPos, len: usize) -> Self {
Self(Repr::Error(ErrorNode::new(message, pos, len)))
} }
/// The type of the node. /// The type of the node.
pub fn kind(&self) -> &SyntaxKind { pub fn kind(&self) -> SyntaxKind {
&self.data().kind match &self.0 {
}
/// Take the kind out of the node.
pub fn take(self) -> SyntaxKind {
match self.0 {
Repr::Leaf(leaf) => leaf.kind, Repr::Leaf(leaf) => leaf.kind,
Repr::Inner(inner) => inner.data.kind.clone(), Repr::Inner(inner) => inner.kind,
Repr::Error(_) => SyntaxKind::Error,
} }
} }
/// The length of the node. /// The byte length of the node in the source text.
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.data().len match &self.0 {
Repr::Leaf(leaf) => leaf.len(),
Repr::Inner(inner) => inner.len,
Repr::Error(error) => error.len,
}
} }
/// The span of the node. /// The span of the node.
pub fn span(&self) -> Span { pub fn span(&self) -> Span {
self.data().span match &self.0 {
Repr::Leaf(leaf) => leaf.span,
Repr::Inner(inner) => inner.span,
Repr::Error(error) => error.span,
}
} }
/// The number of descendants, including the node itself. /// The text of the node if it is a leaf node.
pub fn descendants(&self) -> usize { ///
/// Returns an empty string if this is an inner or error node.
pub fn text(&self) -> &EcoString {
static EMPTY: EcoString = EcoString::new();
match &self.0 { match &self.0 {
Repr::Inner(inner) => inner.descendants, Repr::Leaf(leaf) => &leaf.text,
Repr::Leaf(_) => 1, Repr::Inner(_) | Repr::Error(_) => &EMPTY,
}
}
/// Extract the text from the node.
///
/// Returns an empty string if this is an inner or error node.
pub fn into_text(self) -> EcoString {
match self.0 {
Repr::Leaf(leaf) => leaf.text,
Repr::Inner(_) | Repr::Error(_) => EcoString::new(),
} }
} }
/// The node's children. /// The node's children.
pub fn children(&self) -> std::slice::Iter<'_, SyntaxNode> { pub fn children(&self) -> std::slice::Iter<'_, SyntaxNode> {
match &self.0 { match &self.0 {
Repr::Leaf(_) | Repr::Error(_) => [].iter(),
Repr::Inner(inner) => inner.children.iter(), Repr::Inner(inner) => inner.children.iter(),
Repr::Leaf(_) => [].iter(),
} }
} }
/// Convert the node to a typed AST node. /// Try to convert the node to a typed AST node.
pub fn cast<T>(&self) -> Option<T> pub fn cast<T: AstNode>(&self) -> Option<T> {
where
T: AstNode,
{
T::from_untyped(self) T::from_untyped(self)
} }
/// Get the first child that can cast to the AST type `T`. /// Cast the first child that can cast to the AST type `T`.
pub fn cast_first_child<T: AstNode>(&self) -> Option<T> { pub fn cast_first_match<T: AstNode>(&self) -> Option<T> {
self.children().find_map(Self::cast) self.children().find_map(Self::cast)
} }
/// Get the last child that can cast to the AST type `T`. /// Cast the last child that can cast to the AST type `T`.
pub fn cast_last_child<T: AstNode>(&self) -> Option<T> { pub fn cast_last_match<T: AstNode>(&self) -> Option<T> {
self.children().rev().find_map(Self::cast) self.children().rev().find_map(Self::cast)
} }
/// Whether the node or its children contain an error. /// Whether the node or its children contain an error.
pub fn erroneous(&self) -> bool { pub fn erroneous(&self) -> bool {
match &self.0 { match &self.0 {
Repr::Leaf(_) => false,
Repr::Inner(node) => node.erroneous, Repr::Inner(node) => node.erroneous,
Repr::Leaf(data) => data.kind.is_error(), Repr::Error(_) => true,
} }
} }
@ -102,35 +125,41 @@ impl SyntaxNode {
return vec![]; return vec![];
} }
match self.kind() { if let Repr::Error(error) = &self.0 {
SyntaxKind::Error(pos, message) => { vec![SourceError::new(error.span, error.message.clone()).with_pos(error.pos)]
vec![SourceError::new(self.span(), message.clone()).with_pos(*pos)] } else {
} self.children()
_ => self
.children()
.filter(|node| node.erroneous()) .filter(|node| node.erroneous())
.flat_map(|node| node.errors()) .flat_map(|node| node.errors())
.collect(), .collect()
} }
} }
/// Change the type of the node. /// Change the type of the node.
pub(super) fn convert(&mut self, kind: SyntaxKind) { pub(super) fn convert_to(&mut self, kind: SyntaxKind) {
debug_assert!(!kind.is_error());
match &mut self.0 { match &mut self.0 {
Repr::Leaf(leaf) => leaf.kind = kind,
Repr::Inner(inner) => { Repr::Inner(inner) => {
let node = Arc::make_mut(inner); let node = Arc::make_mut(inner);
node.erroneous |= kind.is_error(); node.kind = kind;
node.data.kind = kind;
} }
Repr::Leaf(leaf) => leaf.kind = kind, Repr::Error(_) => {}
} }
} }
/// Convert the child to an error.
pub(super) fn convert_to_error(&mut self, message: impl Into<EcoString>) {
let len = self.len();
*self = SyntaxNode::error(message, ErrorPos::Full, len);
}
/// Set a synthetic span for the node and all its descendants. /// Set a synthetic span for the node and all its descendants.
pub(super) fn synthesize(&mut self, span: Span) { pub(super) fn synthesize(&mut self, span: Span) {
match &mut self.0 { match &mut self.0 {
Repr::Leaf(leaf) => leaf.span = span,
Repr::Inner(inner) => Arc::make_mut(inner).synthesize(span), Repr::Inner(inner) => Arc::make_mut(inner).synthesize(span),
Repr::Leaf(leaf) => leaf.synthesize(span), Repr::Error(error) => error.span = span,
} }
} }
@ -140,17 +169,25 @@ impl SyntaxNode {
id: SourceId, id: SourceId,
within: Range<u64>, within: Range<u64>,
) -> NumberingResult { ) -> NumberingResult {
match &mut self.0 { if within.start >= within.end {
Repr::Inner(inner) => Arc::make_mut(inner).numberize(id, None, within), return Err(Unnumberable);
Repr::Leaf(leaf) => leaf.numberize(id, within),
} }
let mid = Span::new(id, (within.start + within.end) / 2);
match &mut self.0 {
Repr::Leaf(leaf) => leaf.span = mid,
Repr::Inner(inner) => Arc::make_mut(inner).numberize(id, None, within)?,
Repr::Error(error) => error.span = mid,
}
Ok(())
} }
/// If the span points into this node, convert it to a byte range. /// If the span points into this node, convert it to a byte range.
pub(super) fn range(&self, span: Span, offset: usize) -> Option<Range<usize>> { pub(super) fn range(&self, span: Span, offset: usize) -> Option<Range<usize>> {
match &self.0 { match &self.0 {
Repr::Inner(inner) => inner.range(span, offset), Repr::Inner(inner) => inner.range(span, offset),
Repr::Leaf(leaf) => leaf.range(span, offset), _ => (self.span() == span).then(|| offset..offset + self.len()),
} }
} }
@ -159,10 +196,18 @@ impl SyntaxNode {
matches!(self.0, Repr::Leaf(_)) matches!(self.0, Repr::Leaf(_))
} }
/// The number of descendants, including the node itself.
pub(super) fn descendants(&self) -> usize {
match &self.0 {
Repr::Leaf(_) | Repr::Error(_) => 1,
Repr::Inner(inner) => inner.descendants,
}
}
/// The node's children, mutably. /// The node's children, mutably.
pub(super) fn children_mut(&mut self) -> &mut [SyntaxNode] { pub(super) fn children_mut(&mut self) -> &mut [SyntaxNode] {
match &mut self.0 { match &mut self.0 {
Repr::Leaf(_) => &mut [], Repr::Leaf(_) | Repr::Error(_) => &mut [],
Repr::Inner(inner) => &mut Arc::make_mut(inner).children, Repr::Inner(inner) => &mut Arc::make_mut(inner).children,
} }
} }
@ -199,19 +244,12 @@ impl SyntaxNode {
} }
} }
/// The metadata of the node.
fn data(&self) -> &NodeData {
match &self.0 {
Repr::Inner(inner) => &inner.data,
Repr::Leaf(leaf) => leaf,
}
}
/// The upper bound of assigned numbers in this subtree. /// The upper bound of assigned numbers in this subtree.
fn upper(&self) -> u64 { fn upper(&self) -> u64 {
match &self.0 { match &self.0 {
Repr::Inner(inner) => inner.upper, Repr::Inner(inner) => inner.upper,
Repr::Leaf(leaf) => leaf.span.number() + 1, Repr::Leaf(leaf) => leaf.span.number() + 1,
Repr::Error(error) => error.span.number() + 1,
} }
} }
} }
@ -221,21 +259,64 @@ impl Debug for SyntaxNode {
match &self.0 { match &self.0 {
Repr::Inner(node) => node.fmt(f), Repr::Inner(node) => node.fmt(f),
Repr::Leaf(node) => node.fmt(f), Repr::Leaf(node) => node.fmt(f),
Repr::Error(node) => node.fmt(f),
} }
} }
} }
impl Default for SyntaxNode { impl Default for SyntaxNode {
fn default() -> Self { fn default() -> Self {
Self::leaf(SyntaxKind::None, 0) Self::error("", ErrorPos::Full, 0)
}
}
/// A leaf node in the untyped syntax tree.
#[derive(Clone, Hash)]
struct LeafNode {
/// What kind of node this is (each kind would have its own struct in a
/// strongly typed AST).
kind: SyntaxKind,
/// The source text of the node.
text: EcoString,
/// The node's span.
span: Span,
}
impl LeafNode {
/// Create a new leaf node.
fn new(kind: SyntaxKind, text: impl Into<EcoString>) -> Self {
debug_assert!(!kind.is_error());
Self { kind, text: text.into(), span: Span::detached() }
}
/// The byte length of the node in the source text.
fn len(&self) -> usize {
self.text.len()
}
}
impl Debug for LeafNode {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{:?}: {}", self.kind, self.len())
}
}
impl PartialEq for LeafNode {
fn eq(&self, other: &Self) -> bool {
self.kind == other.kind && self.text == other.text
} }
} }
/// An inner node in the untyped syntax tree. /// An inner node in the untyped syntax tree.
#[derive(Clone, Hash)] #[derive(Clone, Hash)]
struct InnerNode { struct InnerNode {
/// Node metadata. /// What kind of node this is (each kind would have its own struct in a
data: NodeData, /// strongly typed AST).
kind: SyntaxKind,
/// The byte length of the node in the source.
len: usize,
/// The node's span.
span: Span,
/// The number of nodes in the whole subtree, including this node. /// The number of nodes in the whole subtree, including this node.
descendants: usize, descendants: usize,
/// Whether this node or any of its children are erroneous. /// Whether this node or any of its children are erroneous.
@ -248,10 +329,12 @@ struct InnerNode {
impl InnerNode { impl InnerNode {
/// Create a new inner node with the given kind and children. /// Create a new inner node with the given kind and children.
fn with_children(kind: SyntaxKind, children: Vec<SyntaxNode>) -> Self { fn new(kind: SyntaxKind, children: Vec<SyntaxNode>) -> Self {
debug_assert!(!kind.is_error());
let mut len = 0; let mut len = 0;
let mut descendants = 1; let mut descendants = 1;
let mut erroneous = kind.is_error(); let mut erroneous = false;
for child in &children { for child in &children {
len += child.len(); len += child.len();
@ -260,7 +343,9 @@ impl InnerNode {
} }
Self { Self {
data: NodeData::new(kind, len), kind,
len,
span: Span::detached(),
descendants, descendants,
erroneous, erroneous,
upper: 0, upper: 0,
@ -270,7 +355,7 @@ impl InnerNode {
/// Set a synthetic span for the node and all its descendants. /// Set a synthetic span for the node and all its descendants.
fn synthesize(&mut self, span: Span) { fn synthesize(&mut self, span: Span) {
self.data.synthesize(span); self.span = span;
for child in &mut self.children { for child in &mut self.children {
child.synthesize(span); child.synthesize(span);
} }
@ -310,7 +395,7 @@ impl InnerNode {
let mut start = within.start; let mut start = within.start;
if range.is_none() { if range.is_none() {
let end = start + stride; let end = start + stride;
self.data.numberize(id, start..end)?; self.span = Span::new(id, (start + end) / 2);
self.upper = within.end; self.upper = within.end;
start = end; start = end;
} }
@ -329,14 +414,14 @@ impl InnerNode {
/// If the span points into this node, convert it to a byte range. /// If the span points into this node, convert it to a byte range.
fn range(&self, span: Span, mut offset: usize) -> Option<Range<usize>> { fn range(&self, span: Span, mut offset: usize) -> Option<Range<usize>> {
// Check whether we found it. // Check whether we found it.
if let Some(range) = self.data.range(span, offset) { if span == self.span {
return Some(range); return Some(offset..offset + self.len);
} }
// The parent of a subtree has a smaller span number than all of its // The parent of a subtree has a smaller span number than all of its
// descendants. Therefore, we can bail out early if the target span's // descendants. Therefore, we can bail out early if the target span's
// number is smaller than our number. // number is smaller than our number.
if span.number() < self.data.span.number() { if span.number() < self.span.number() {
return None; return None;
} }
@ -371,8 +456,7 @@ impl InnerNode {
let superseded = &self.children[range.clone()]; let superseded = &self.children[range.clone()];
// Compute the new byte length. // Compute the new byte length.
self.data.len = self.data.len self.len = self.len + replacement.iter().map(SyntaxNode::len).sum::<usize>()
+ replacement.iter().map(SyntaxNode::len).sum::<usize>()
- superseded.iter().map(SyntaxNode::len).sum::<usize>(); - superseded.iter().map(SyntaxNode::len).sum::<usize>();
// Compute the new number of descendants. // Compute the new number of descendants.
@ -412,7 +496,7 @@ impl InnerNode {
.start .start
.checked_sub(1) .checked_sub(1)
.and_then(|i| self.children.get(i)) .and_then(|i| self.children.get(i))
.map_or(self.data.span.number() + 1, |child| child.upper()); .map_or(self.span.number() + 1, |child| child.upper());
// The upper bound for renumbering is either // The upper bound for renumbering is either
// - the span number of the first child after the to-be-renumbered // - the span number of the first child after the to-be-renumbered
@ -426,7 +510,7 @@ impl InnerNode {
// Try to renumber. // Try to renumber.
let within = start_number..end_number; let within = start_number..end_number;
let id = self.data.span.source(); let id = self.span.source();
if self.numberize(id, Some(renumber), within).is_ok() { if self.numberize(id, Some(renumber), within).is_ok() {
return Ok(()); return Ok(());
} }
@ -450,7 +534,7 @@ impl InnerNode {
prev_descendants: usize, prev_descendants: usize,
new_descendants: usize, new_descendants: usize,
) { ) {
self.data.len = self.data.len + new_len - prev_len; self.len = self.len + new_len - prev_len;
self.descendants = self.descendants + new_descendants - prev_descendants; self.descendants = self.descendants + new_descendants - prev_descendants;
self.erroneous = self.children.iter().any(SyntaxNode::erroneous); self.erroneous = self.children.iter().any(SyntaxNode::erroneous);
} }
@ -458,7 +542,7 @@ impl InnerNode {
impl Debug for InnerNode { impl Debug for InnerNode {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
self.data.fmt(f)?; write!(f, "{:?}: {}", self.kind, self.len)?;
if !self.children.is_empty() { if !self.children.is_empty() {
f.write_str(" ")?; f.write_str(" ")?;
f.debug_list().entries(&self.children).finish()?; f.debug_list().entries(&self.children).finish()?;
@ -469,64 +553,62 @@ impl Debug for InnerNode {
impl PartialEq for InnerNode { impl PartialEq for InnerNode {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.data == other.data self.kind == other.kind
&& self.len == other.len
&& self.descendants == other.descendants && self.descendants == other.descendants
&& self.erroneous == other.erroneous && self.erroneous == other.erroneous
&& self.children == other.children && self.children == other.children
} }
} }
/// Data shared between leaf and inner nodes. /// An error node in the untyped syntax tree.
#[derive(Clone, Hash)] #[derive(Clone, Hash)]
struct NodeData { struct ErrorNode {
/// What kind of node this is (each kind would have its own struct in a /// The error message.
/// strongly typed AST). message: EcoString,
kind: SyntaxKind, /// Where in the node an error should be annotated.
/// The byte length of the node in the source. pos: ErrorPos,
/// The byte length of the error in the source.
len: usize, len: usize,
/// The node's span. /// The node's span.
span: Span, span: Span,
} }
impl NodeData { impl ErrorNode {
/// Create new node metadata. /// Create new error node.
fn new(kind: SyntaxKind, len: usize) -> Self { fn new(message: impl Into<EcoString>, pos: ErrorPos, len: usize) -> Self {
Self { len, kind, span: Span::detached() } Self {
} message: message.into(),
pos,
/// Set a synthetic span for the node. len,
fn synthesize(&mut self, span: Span) { span: Span::detached(),
self.span = span;
}
/// Assign a span to the node.
fn numberize(&mut self, id: SourceId, within: Range<u64>) -> NumberingResult {
if within.start < within.end {
self.span = Span::new(id, (within.start + within.end) / 2);
Ok(())
} else {
Err(Unnumberable)
} }
} }
/// If the span points into this node, convert it to a byte range.
fn range(&self, span: Span, offset: usize) -> Option<Range<usize>> {
(self.span == span).then(|| offset..offset + self.len)
}
} }
impl Debug for NodeData { impl Debug for ErrorNode {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{:?}: {}", self.kind, self.len) write!(f, "({}): {}", self.message, self.len)
} }
} }
impl PartialEq for NodeData { impl PartialEq for ErrorNode {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.kind == other.kind && self.len == other.len self.message == other.message && self.pos == other.pos && self.len == other.len
} }
} }
/// Where in a node an error should be annotated,
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum ErrorPos {
/// Over the full width of the node.
Full,
/// At the start of the node.
Start,
/// At the end of the node.
End,
}
/// A syntax node in a context. /// A syntax node in a context.
/// ///
/// Knows its exact offset in the file and provides access to its /// Knows its exact offset in the file and provides access to its
@ -542,7 +624,7 @@ pub struct LinkedNode<'a> {
} }
impl<'a> LinkedNode<'a> { impl<'a> LinkedNode<'a> {
/// Start a new traversal at the source's root node. /// Start a new traversal at a root node.
pub fn new(root: &'a SyntaxNode) -> Self { pub fn new(root: &'a SyntaxNode) -> Self {
Self { node: root, parent: None, index: 0, offset: 0 } Self { node: root, parent: None, index: 0, offset: 0 }
} }
@ -557,17 +639,17 @@ impl<'a> LinkedNode<'a> {
self.index self.index
} }
/// The absolute byte offset of the this node in the source file. /// The absolute byte offset of this node in the source file.
pub fn offset(&self) -> usize { pub fn offset(&self) -> usize {
self.offset self.offset
} }
/// The byte range of the this node in the source file. /// The byte range of this node in the source file.
pub fn range(&self) -> Range<usize> { pub fn range(&self) -> Range<usize> {
self.offset..self.offset + self.node.len() self.offset..self.offset + self.node.len()
} }
/// Get this node's children. /// An iterator over this node's children.
pub fn children(&self) -> LinkedChildren<'a> { pub fn children(&self) -> LinkedChildren<'a> {
LinkedChildren { LinkedChildren {
parent: Rc::new(self.clone()), parent: Rc::new(self.clone()),
@ -586,7 +668,7 @@ impl<'a> LinkedNode<'a> {
} }
/// Get the kind of this node's parent. /// Get the kind of this node's parent.
pub fn parent_kind(&self) -> Option<&'a SyntaxKind> { pub fn parent_kind(&self) -> Option<SyntaxKind> {
Some(self.parent()?.node.kind()) Some(self.parent()?.node.kind())
} }
@ -648,7 +730,7 @@ impl<'a> LinkedNode<'a> {
None None
} }
/// Get the leaf at the specified cursor position. /// Get the leaf at the specified byte offset.
pub fn leaf_at(&self, cursor: usize) -> Option<Self> { pub fn leaf_at(&self, cursor: usize) -> Option<Self> {
if self.node.children().len() == 0 && cursor <= self.offset + self.len() { if self.node.children().len() == 0 && cursor <= self.offset + self.len() {
return Some(self.clone()); return Some(self.clone());
@ -784,13 +866,13 @@ mod tests {
let node = LinkedNode::new(source.root()).leaf_at(7).unwrap(); let node = LinkedNode::new(source.root()).leaf_at(7).unwrap();
assert_eq!(node.offset(), 5); assert_eq!(node.offset(), 5);
assert_eq!(node.len(), 4); assert_eq!(node.len(), 4);
assert_eq!(node.kind(), &SyntaxKind::Ident("text".into())); assert_eq!(node.kind(), SyntaxKind::Ident);
// Go back to "#set". Skips the space. // Go back to "#set". Skips the space.
let prev = node.prev_sibling().unwrap(); let prev = node.prev_sibling().unwrap();
assert_eq!(prev.offset(), 0); assert_eq!(prev.offset(), 0);
assert_eq!(prev.len(), 4); assert_eq!(prev.len(), 4);
assert_eq!(prev.kind(), &SyntaxKind::Set); assert_eq!(prev.kind(), SyntaxKind::Set);
} }
#[test] #[test]
@ -798,15 +880,15 @@ mod tests {
let source = Source::detached("#set fun(12pt, red)"); let source = Source::detached("#set fun(12pt, red)");
let leaf = LinkedNode::new(source.root()).leaf_at(6).unwrap(); let leaf = LinkedNode::new(source.root()).leaf_at(6).unwrap();
let prev = leaf.prev_leaf().unwrap(); let prev = leaf.prev_leaf().unwrap();
assert_eq!(leaf.kind(), &SyntaxKind::Ident("fun".into())); assert_eq!(leaf.kind(), SyntaxKind::Ident);
assert_eq!(prev.kind(), &SyntaxKind::Set); assert_eq!(prev.kind(), SyntaxKind::Set);
let source = Source::detached("#let x = 10"); let source = Source::detached("#let x = 10");
let leaf = LinkedNode::new(source.root()).leaf_at(9).unwrap(); let leaf = LinkedNode::new(source.root()).leaf_at(9).unwrap();
let prev = leaf.prev_leaf().unwrap(); let prev = leaf.prev_leaf().unwrap();
let next = leaf.next_leaf().unwrap(); let next = leaf.next_leaf().unwrap();
assert_eq!(prev.kind(), &SyntaxKind::Eq); assert_eq!(prev.kind(), SyntaxKind::Eq);
assert_eq!(leaf.kind(), &SyntaxKind::Space { newlines: 0 }); assert_eq!(leaf.kind(), SyntaxKind::Space { newlines: 0 });
assert_eq!(next.kind(), &SyntaxKind::Int(10)); assert_eq!(next.kind(), SyntaxKind::Int);
} }
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -87,8 +87,8 @@ fn try_reparse(
// reject text that points to the special case for URL // reject text that points to the special case for URL
// evasion and line comments. // evasion and line comments.
if !child.kind().is_space() if !child.kind().is_space()
&& child.kind() != &SyntaxKind::Semicolon && child.kind() != SyntaxKind::Semicolon
&& child.kind() != &SyntaxKind::Text('/'.into()) && (child.kind() != SyntaxKind::Text || child.text() != "/")
&& (ahead.is_none() || change.replaced.start > child_span.end) && (ahead.is_none() || change.replaced.start > child_span.end)
&& !ahead.map_or(false, Ahead::is_compulsory) && !ahead.map_or(false, Ahead::is_compulsory)
{ {
@ -177,7 +177,7 @@ fn try_reparse(
// Make sure this is a markup node and that we may replace. If so, save // Make sure this is a markup node and that we may replace. If so, save
// the current indent. // the current indent.
let min_indent = match node.kind() { let min_indent = match node.kind() {
SyntaxKind::Markup { min_indent } if safe_to_replace => *min_indent, SyntaxKind::Markup { min_indent } if safe_to_replace => min_indent,
_ => return None, _ => return None,
}; };
@ -375,23 +375,23 @@ enum ReparseMode {
/// Whether changes _inside_ this node are safely encapsulated, so that only /// Whether changes _inside_ this node are safely encapsulated, so that only
/// this node must be reparsed. /// this node must be reparsed.
fn is_bounded(kind: &SyntaxKind) -> bool { fn is_bounded(kind: SyntaxKind) -> bool {
matches!( matches!(
kind, kind,
SyntaxKind::CodeBlock SyntaxKind::CodeBlock
| SyntaxKind::ContentBlock | SyntaxKind::ContentBlock
| SyntaxKind::Linebreak | SyntaxKind::Linebreak
| SyntaxKind::SmartQuote { .. } | SyntaxKind::SmartQuote
| SyntaxKind::BlockComment | SyntaxKind::BlockComment
| SyntaxKind::Space { .. } | SyntaxKind::Space { .. }
| SyntaxKind::Escape(_) | SyntaxKind::Escape
| SyntaxKind::Shorthand(_) | SyntaxKind::Shorthand
) )
} }
/// Whether `at_start` would still be true after this node given the /// Whether `at_start` would still be true after this node given the
/// previous value of the property. /// previous value of the property.
fn next_at_start(kind: &SyntaxKind, prev: bool) -> bool { fn next_at_start(kind: SyntaxKind, prev: bool) -> bool {
match kind { match kind {
SyntaxKind::Space { newlines: (1..) } => true, SyntaxKind::Space { newlines: (1..) } => true,
SyntaxKind::Space { .. } | SyntaxKind::LineComment | SyntaxKind::BlockComment => { SyntaxKind::Space { .. } | SyntaxKind::LineComment | SyntaxKind::BlockComment => {

View File

@ -1,233 +0,0 @@
use unscanny::Scanner;
use super::{is_ident, is_newline, RawFields};
use crate::util::EcoString;
/// Resolve all escape sequences in a string.
pub fn resolve_string(string: &str) -> EcoString {
let mut out = EcoString::with_capacity(string.len());
let mut s = Scanner::new(string);
while let Some(c) = s.eat() {
if c != '\\' {
out.push(c);
continue;
}
let start = s.locate(-1);
match s.eat() {
Some('\\') => out.push('\\'),
Some('"') => out.push('"'),
Some('n') => out.push('\n'),
Some('r') => out.push('\r'),
Some('t') => out.push('\t'),
Some('u') if s.eat_if('{') => {
// TODO: Error if closing brace is missing.
let sequence = s.eat_while(char::is_ascii_hexdigit);
let _terminated = s.eat_if('}');
match resolve_hex(sequence) {
Some(c) => out.push(c),
None => out.push_str(s.from(start)),
}
}
_ => out.push_str(s.from(start)),
}
}
out
}
/// Resolve a hexadecimal escape sequence into a character
/// (only the inner hex letters without braces or `\u`).
pub fn resolve_hex(sequence: &str) -> Option<char> {
u32::from_str_radix(sequence, 16).ok().and_then(std::char::from_u32)
}
/// Resolve the language tag and trim the raw text.
pub fn resolve_raw(column: usize, backticks: usize, text: &str) -> RawFields {
if backticks > 1 {
let (tag, inner) = split_at_lang_tag(text);
let (text, block) = trim_and_split_raw(column, inner);
RawFields {
lang: is_ident(tag).then(|| tag.into()),
text: text.into(),
block,
}
} else {
RawFields {
lang: None,
text: split_lines(text).join("\n").into(),
block: false,
}
}
}
/// Parse the lang tag and return it alongside the remaining inner raw text.
fn split_at_lang_tag(raw: &str) -> (&str, &str) {
let mut s = Scanner::new(raw);
(s.eat_until(|c: char| c == '`' || c.is_whitespace() || is_newline(c)), s.after())
}
/// Trim raw text and splits it into lines.
///
/// Also returns whether at least one newline was contained in `raw`.
fn trim_and_split_raw(column: usize, mut raw: &str) -> (String, bool) {
// Trims one space at the start.
raw = raw.strip_prefix(' ').unwrap_or(raw);
// Trim one space at the end if the last non-whitespace char is a backtick.
if raw.trim_end().ends_with('`') {
raw = raw.strip_suffix(' ').unwrap_or(raw);
}
let mut lines = split_lines(raw);
// Dedent based on column, but not for the first line.
for line in lines.iter_mut().skip(1) {
let offset = line
.chars()
.take(column)
.take_while(|c| c.is_whitespace())
.map(char::len_utf8)
.sum();
*line = &line[offset..];
}
let had_newline = lines.len() > 1;
let is_whitespace = |line: &&str| line.chars().all(char::is_whitespace);
// Trims a sequence of whitespace followed by a newline at the start.
if lines.first().map_or(false, is_whitespace) {
lines.remove(0);
}
// Trims a newline followed by a sequence of whitespace at the end.
if lines.last().map_or(false, is_whitespace) {
lines.pop();
}
(lines.join("\n"), had_newline)
}
/// Split a string into a vector of lines
/// (respecting Unicode, Unix, Mac and Windows line breaks).
fn split_lines(text: &str) -> Vec<&str> {
let mut s = Scanner::new(text);
let mut lines = Vec::new();
let mut start = 0;
let mut end = 0;
while let Some(c) = s.eat() {
if is_newline(c) {
if c == '\r' {
s.eat_if('\n');
}
lines.push(&text[start..end]);
start = s.cursor();
}
end = s.cursor();
}
lines.push(&text[start..]);
lines
}
#[cfg(test)]
#[rustfmt::skip]
mod tests {
use super::*;
#[test]
fn test_resolve_strings() {
#[track_caller]
fn test(string: &str, expected: &str) {
assert_eq!(resolve_string(string), expected);
}
test(r#"hello world"#, "hello world");
test(r#"hello\nworld"#, "hello\nworld");
test(r#"a\"bc"#, "a\"bc");
test(r#"a\u{2603}bc"#, "a☃bc");
test(r#"a\u{26c3bg"#, "a𦰻g");
test(r#"av\u{6797"#, "av林");
test(r#"a\\"#, "a\\");
test(r#"a\\\nbc"#, "a\\\nbc");
test(r#"a\t\r\nbc"#, "a\t\r\nbc");
test(r"🌎", "🌎");
test(r"🌎\", r"🌎\");
test(r"\🌎", r"\🌎");
}
#[test]
fn test_split_at_lang_tag() {
#[track_caller]
fn test(text: &str, lang: &str, inner: &str) {
assert_eq!(split_at_lang_tag(text), (lang, inner));
}
test("typst it!", "typst", " it!");
test("typst\n it!", "typst", "\n it!");
test("typst\n it!", "typst", "\n it!");
test("abc`", "abc", "`");
test(" hi", "", " hi");
test("`", "", "`");
}
#[test]
fn test_resolve_raw() {
#[track_caller]
fn test(
column: usize,
backticks: usize,
raw: &str,
lang: Option<&str>,
text: &str,
block: bool,
) {
let node = resolve_raw(column, backticks, raw);
assert_eq!(node.lang.as_deref(), lang);
assert_eq!(node.text, text);
assert_eq!(node.block, block);
}
// Just one backtick.
test(0, 1, "py", None, "py", false);
test(0, 1, "1\n2", None, "1\n2", false);
test(0, 1, "1\r\n2", None, "1\n2", false);
// More than one backtick with lang tag.
test(0, 2, "js alert()", Some("js"), "alert()", false);
test(0, 3, "py quit(\n\n)", Some("py"), "quit(\n\n)", true);
test(0, 2, "", None, "", false);
// Trimming of whitespace (tested more thoroughly in separate test).
test(0, 2, " a", None, "a", false);
test(0, 2, " a", None, " a", false);
test(0, 2, " \na", None, "a", true);
// Dedenting
test(2, 3, " def foo():\n bar()", None, "def foo():\n bar()", true);
}
#[test]
fn test_trim_raw() {
#[track_caller]
fn test(text: &str, expected: &str) {
assert_eq!(trim_and_split_raw(0, text).0, expected);
}
test(" hi", "hi");
test(" hi", " hi");
test("\nhi", "hi");
test(" \n hi", " hi");
test("hi` ", "hi`");
test("hi` ", "hi` ");
test("hi` ", "hi` ");
test("hi ", "hi ");
test("hi ", "hi ");
test("hi\n", "hi");
test("hi \n ", "hi ");
test(" \n hi \n ", " hi ");
}
}

View File

@ -8,10 +8,10 @@ use std::path::{Path, PathBuf};
use comemo::Prehashed; use comemo::Prehashed;
use unscanny::Scanner; use unscanny::Scanner;
use super::ast::Markup;
use super::reparse::reparse;
use super::{is_newline, parse, Span, SyntaxNode};
use crate::diag::SourceResult; use crate::diag::SourceResult;
use crate::syntax::ast::Markup;
use crate::syntax::{is_newline, parse, reparse};
use crate::syntax::{Span, SyntaxNode};
use crate::util::{PathExt, StrExt}; use crate::util::{PathExt, StrExt};
/// A source file. /// A source file.
@ -124,11 +124,8 @@ impl Source {
} }
// Recalculate the line starts after the edit. // Recalculate the line starts after the edit.
self.lines.extend(lines_from( self.lines
start_byte, .extend(lines_from(start_byte, start_utf16, &self.text[start_byte..]));
start_utf16,
&self.text[start_byte..],
));
// Incrementally reparse the replaced range. // Incrementally reparse the replaced range.
let mut root = std::mem::take(&mut self.root).into_inner(); let mut root = std::mem::take(&mut self.root).into_inner();

View File

@ -5,7 +5,7 @@ use iai::{black_box, main, Iai};
use typst::diag::{FileError, FileResult}; use typst::diag::{FileError, FileResult};
use typst::font::{Font, FontBook}; use typst::font::{Font, FontBook};
use typst::model::Library; use typst::model::Library;
use typst::syntax::{LexMode, Lexer, Source, SourceId}; use typst::syntax::{Source, SourceId};
use typst::util::Buffer; use typst::util::Buffer;
use typst::World; use typst::World;
use unscanny::Scanner; use unscanny::Scanner;
@ -16,7 +16,6 @@ const FONT: &[u8] = include_bytes!("../fonts/IBMPlexSans-Regular.ttf");
main!( main!(
bench_decode, bench_decode,
bench_scan, bench_scan,
bench_lex,
bench_parse, bench_parse,
bench_edit, bench_edit,
bench_eval, bench_eval,
@ -49,10 +48,6 @@ fn bench_scan(iai: &mut Iai) {
}) })
} }
fn bench_lex(iai: &mut Iai) {
iai.run(|| Lexer::new(black_box(TEXT), black_box(LexMode::Markup)).count());
}
fn bench_parse(iai: &mut Iai) { fn bench_parse(iai: &mut Iai) {
iai.run(|| typst::syntax::parse(TEXT)); iai.run(|| typst::syntax::parse(TEXT));
} }

View File

@ -24,5 +24,5 @@ $ A sub:eq:not B $
<table> <table>
--- ---
// Error: 8 expected closing paren // Error: 8 expected math atom
$ sum_( $ $ sum_( $