mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Remove most fields from SyntaxKind
enum
This commit is contained in:
parent
15f0434d1f
commit
40561e57fb
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(¶m.cast, string);
|
if let Some(docs) = find_string_doc(¶m.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();
|
||||||
|
@ -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 => {}
|
||||||
|
@ -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 {
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 == '-'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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::*;
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1311
src/syntax/parser.rs
1311
src/syntax/parser.rs
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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 => {
|
@ -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 ");
|
|
||||||
}
|
|
||||||
}
|
|
@ -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();
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
@ -24,5 +24,5 @@ $ A sub:eq:not B $
|
|||||||
<table>
|
<table>
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 8 expected closing paren
|
// Error: 8 expected math atom
|
||||||
$ sum_( $
|
$ sum_( $
|
||||||
|
Loading…
x
Reference in New Issue
Block a user