Hashtags everywhere!
This commit is contained in:
parent
33585d9a3f
commit
a8fd64f928
@ -189,11 +189,11 @@ fn items() -> LangItems {
|
||||
term_item: |term, description| {
|
||||
layout::ListItem::Term(basics::TermItem { term, description }).pack()
|
||||
},
|
||||
math_formula: |body, block| math::FormulaNode { body, block }.pack(),
|
||||
formula: |body, block| math::FormulaNode { body, block }.pack(),
|
||||
math_atom: |atom| math::AtomNode(atom).pack(),
|
||||
math_delimited: |body| math::LrNode(body).pack(),
|
||||
math_align_point: || math::AlignPointNode.pack(),
|
||||
math_delimited: |open, body, close| math::LrNode(open + body + close).pack(),
|
||||
math_script: |base, sub, sup| math::ScriptNode { base, sub, sup }.pack(),
|
||||
math_frac: |num, denom| math::FracNode { num, denom }.pack(),
|
||||
math_align_point: || math::AlignPointNode.pack(),
|
||||
}
|
||||
}
|
||||
|
@ -133,7 +133,7 @@ fn attachment(ctx: &MathContext, id: GlyphId, italics_correction: Abs) -> Abs {
|
||||
|
||||
/// Extract a single character from content.
|
||||
fn extract(accent: &Content) -> Option<char> {
|
||||
let atom = accent.to::<FormulaNode>()?.body.to::<AtomNode>()?;
|
||||
let atom = accent.to::<AtomNode>()?;
|
||||
let mut chars = atom.0.chars();
|
||||
let c = chars.next().filter(|_| chars.next().is_none())?;
|
||||
Some(combining(c))
|
||||
@ -166,6 +166,8 @@ fn combining(c: char) -> char {
|
||||
'\u{2190}' => '\u{20d6}',
|
||||
'\u{2192}' => '\u{20d7}',
|
||||
'\u{2212}' => '\u{0305}',
|
||||
'\u{223C}' => '\u{0303}',
|
||||
'\u{22C5}' => '\u{0307}',
|
||||
'\u{27f6}' => '\u{20d7}',
|
||||
_ => c,
|
||||
}
|
||||
|
@ -155,7 +155,7 @@ fn layout(
|
||||
|
||||
/// Select a precomposed radical, if the font has it.
|
||||
fn precomposed(ctx: &MathContext, index: Option<&Content>, target: Abs) -> Option<Frame> {
|
||||
let node = index?.to::<FormulaNode>()?.body.to::<AtomNode>()?;
|
||||
let node = index?.to::<AtomNode>()?;
|
||||
let c = match node.0.as_str() {
|
||||
"3" => '∛',
|
||||
"4" => '∜',
|
||||
|
@ -7,7 +7,7 @@ pub enum Category {
|
||||
Comment,
|
||||
/// Punctuation in code.
|
||||
Punctuation,
|
||||
/// An escape sequence, shorthand or symbol notation.
|
||||
/// An escape sequence or shorthand.
|
||||
Escape,
|
||||
/// Strong markup.
|
||||
Strong,
|
||||
@ -97,7 +97,6 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
|
||||
SyntaxKind::Parbreak => None,
|
||||
SyntaxKind::Escape => Some(Category::Escape),
|
||||
SyntaxKind::Shorthand => Some(Category::Escape),
|
||||
SyntaxKind::Symbol => Some(Category::Escape),
|
||||
SyntaxKind::SmartQuote => None,
|
||||
SyntaxKind::Strong => Some(Category::Strong),
|
||||
SyntaxKind::Emph => Some(Category::Emph),
|
||||
@ -113,12 +112,22 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
|
||||
SyntaxKind::EnumMarker => Some(Category::ListMarker),
|
||||
SyntaxKind::TermItem => None,
|
||||
SyntaxKind::TermMarker => Some(Category::ListMarker),
|
||||
SyntaxKind::Formula => None,
|
||||
|
||||
SyntaxKind::Math => None,
|
||||
SyntaxKind::Atom => None,
|
||||
SyntaxKind::Delimited => None,
|
||||
SyntaxKind::Script => None,
|
||||
SyntaxKind::Frac => None,
|
||||
SyntaxKind::AlignPoint => Some(Category::MathOperator),
|
||||
SyntaxKind::MathAtom => None,
|
||||
SyntaxKind::MathIdent => highlight_ident(node),
|
||||
SyntaxKind::MathDelimited => None,
|
||||
SyntaxKind::MathScript => None,
|
||||
SyntaxKind::MathFrac => None,
|
||||
SyntaxKind::MathAlignPoint => Some(Category::MathOperator),
|
||||
|
||||
SyntaxKind::Hashtag if node.before_error() => None,
|
||||
SyntaxKind::Hashtag => node
|
||||
.next_leaf()
|
||||
.filter(|node| node.kind() != SyntaxKind::Dollar)
|
||||
.as_ref()
|
||||
.and_then(highlight),
|
||||
|
||||
SyntaxKind::LeftBrace => Some(Category::Punctuation),
|
||||
SyntaxKind::RightBrace => Some(Category::Punctuation),
|
||||
@ -134,14 +143,14 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
|
||||
_ => Some(Category::Operator),
|
||||
},
|
||||
SyntaxKind::Underscore => match node.parent_kind() {
|
||||
Some(SyntaxKind::Script) => Some(Category::MathOperator),
|
||||
Some(SyntaxKind::MathScript) => Some(Category::MathOperator),
|
||||
_ => None,
|
||||
},
|
||||
SyntaxKind::Dollar => Some(Category::MathDelimiter),
|
||||
SyntaxKind::Plus => Some(Category::Operator),
|
||||
SyntaxKind::Minus => Some(Category::Operator),
|
||||
SyntaxKind::Slash => Some(match node.parent_kind() {
|
||||
Some(SyntaxKind::Frac) => Category::MathOperator,
|
||||
Some(SyntaxKind::MathFrac) => Category::MathOperator,
|
||||
_ => Category::Operator,
|
||||
}),
|
||||
SyntaxKind::Hat => Some(Category::MathOperator),
|
||||
@ -183,47 +192,8 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
|
||||
SyntaxKind::Include => Some(Category::Keyword),
|
||||
SyntaxKind::As => Some(Category::Keyword),
|
||||
|
||||
SyntaxKind::Ident => match node.parent_kind() {
|
||||
Some(
|
||||
SyntaxKind::Markup
|
||||
| SyntaxKind::Math
|
||||
| SyntaxKind::Script
|
||||
| SyntaxKind::Frac,
|
||||
) => Some(Category::Interpolated),
|
||||
Some(SyntaxKind::FuncCall) => Some(Category::Function),
|
||||
Some(SyntaxKind::FieldAccess)
|
||||
if node.parent().and_then(|p| p.parent_kind())
|
||||
== Some(SyntaxKind::SetRule)
|
||||
&& node.next_sibling().is_none() =>
|
||||
{
|
||||
Some(Category::Function)
|
||||
}
|
||||
Some(SyntaxKind::FieldAccess)
|
||||
if node
|
||||
.parent()
|
||||
.and_then(|p| p.parent())
|
||||
.filter(|gp| gp.kind() == SyntaxKind::Parenthesized)
|
||||
.and_then(|gp| gp.parent())
|
||||
.map_or(false, |ggp| ggp.kind() == SyntaxKind::FuncCall)
|
||||
&& node.next_sibling().is_none() =>
|
||||
{
|
||||
Some(Category::Function)
|
||||
}
|
||||
Some(SyntaxKind::MethodCall) if node.prev_sibling().is_some() => {
|
||||
Some(Category::Function)
|
||||
}
|
||||
Some(SyntaxKind::Closure) if node.prev_sibling().is_none() => {
|
||||
Some(Category::Function)
|
||||
}
|
||||
Some(SyntaxKind::SetRule) => Some(Category::Function),
|
||||
Some(SyntaxKind::ShowRule)
|
||||
if node.prev_sibling().as_ref().map(|v| v.kind())
|
||||
== Some(SyntaxKind::Show) =>
|
||||
{
|
||||
Some(Category::Function)
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
SyntaxKind::Code => None,
|
||||
SyntaxKind::Ident => highlight_ident(node),
|
||||
SyntaxKind::Bool => Some(Category::Keyword),
|
||||
SyntaxKind::Int => Some(Category::Number),
|
||||
SyntaxKind::Float => Some(Category::Number),
|
||||
@ -238,7 +208,11 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
|
||||
SyntaxKind::Keyed => None,
|
||||
SyntaxKind::Unary => None,
|
||||
SyntaxKind::Binary => None,
|
||||
SyntaxKind::FieldAccess => None,
|
||||
SyntaxKind::FieldAccess => match node.parent_kind() {
|
||||
Some(SyntaxKind::Markup | SyntaxKind::Math) => Some(Category::Interpolated),
|
||||
Some(SyntaxKind::FieldAccess) => node.parent().and_then(highlight),
|
||||
_ => None,
|
||||
},
|
||||
SyntaxKind::FuncCall => None,
|
||||
SyntaxKind::MethodCall => None,
|
||||
SyntaxKind::Args => None,
|
||||
@ -266,6 +240,52 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Highlight an identifier based on context.
|
||||
fn highlight_ident(node: &LinkedNode) -> Option<Category> {
|
||||
match node.parent_kind() {
|
||||
Some(
|
||||
SyntaxKind::Markup
|
||||
| SyntaxKind::Math
|
||||
| SyntaxKind::MathFrac
|
||||
| SyntaxKind::MathScript,
|
||||
) => Some(Category::Interpolated),
|
||||
Some(SyntaxKind::FuncCall) => Some(Category::Function),
|
||||
Some(SyntaxKind::FieldAccess)
|
||||
if node.parent().and_then(|p| p.parent_kind())
|
||||
== Some(SyntaxKind::SetRule)
|
||||
&& node.next_sibling().is_none() =>
|
||||
{
|
||||
Some(Category::Function)
|
||||
}
|
||||
Some(SyntaxKind::FieldAccess)
|
||||
if node
|
||||
.parent()
|
||||
.and_then(|p| p.parent())
|
||||
.filter(|gp| gp.kind() == SyntaxKind::Parenthesized)
|
||||
.and_then(|gp| gp.parent())
|
||||
.map_or(false, |ggp| ggp.kind() == SyntaxKind::FuncCall)
|
||||
&& node.next_sibling().is_none() =>
|
||||
{
|
||||
Some(Category::Function)
|
||||
}
|
||||
Some(SyntaxKind::FieldAccess) => node.parent().and_then(highlight),
|
||||
Some(SyntaxKind::MethodCall) if node.prev_sibling().is_some() => {
|
||||
Some(Category::Function)
|
||||
}
|
||||
Some(SyntaxKind::Closure) if node.prev_sibling().is_none() => {
|
||||
Some(Category::Function)
|
||||
}
|
||||
Some(SyntaxKind::SetRule) => Some(Category::Function),
|
||||
Some(SyntaxKind::ShowRule)
|
||||
if node.prev_sibling().as_ref().map(|v| v.kind())
|
||||
== Some(SyntaxKind::Show) =>
|
||||
{
|
||||
Some(Category::Function)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ops::Range;
|
||||
@ -300,7 +320,8 @@ mod tests {
|
||||
test(
|
||||
"#f(x + 1)",
|
||||
&[
|
||||
(0..2, Function),
|
||||
(0..1, Function),
|
||||
(1..2, Function),
|
||||
(2..3, Punctuation),
|
||||
(5..6, Operator),
|
||||
(7..8, Number),
|
||||
@ -311,7 +332,8 @@ mod tests {
|
||||
test(
|
||||
"#let f(x) = x",
|
||||
&[
|
||||
(0..4, Keyword),
|
||||
(0..1, Keyword),
|
||||
(1..4, Keyword),
|
||||
(5..6, Function),
|
||||
(6..7, Punctuation),
|
||||
(8..9, Punctuation),
|
||||
|
@ -234,7 +234,7 @@ fn eval_markup(
|
||||
*node = mem::take(node).labelled(label);
|
||||
}
|
||||
}
|
||||
value => seq.push(value.display().spanned(expr.span())),
|
||||
value => seq.push(value.display()),
|
||||
},
|
||||
}
|
||||
|
||||
@ -254,11 +254,9 @@ impl Eval for ast::Expr {
|
||||
type Output = Value;
|
||||
|
||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let span = self.span();
|
||||
let forbidden = |name| {
|
||||
error!(
|
||||
self.span(),
|
||||
"{} is only allowed directly in code and content blocks", name
|
||||
)
|
||||
error!(span, "{} is only allowed directly in code and content blocks", name)
|
||||
};
|
||||
|
||||
match self {
|
||||
@ -266,9 +264,9 @@ impl Eval for ast::Expr {
|
||||
Self::Space(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Linebreak(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Parbreak(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Escape(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Shorthand(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Symbol(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Escape(v) => v.eval(vm),
|
||||
Self::Shorthand(v) => v.eval(vm),
|
||||
Self::SmartQuote(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Strong(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Emph(v) => v.eval(vm).map(Value::Content),
|
||||
@ -280,11 +278,14 @@ impl Eval for ast::Expr {
|
||||
Self::List(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Enum(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Term(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Atom(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Delimited(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Script(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Frac(v) => v.eval(vm).map(Value::Content),
|
||||
Self::AlignPoint(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Formula(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Math(v) => v.eval(vm).map(Value::Content),
|
||||
Self::MathAtom(v) => v.eval(vm).map(Value::Content),
|
||||
Self::MathIdent(v) => v.eval(vm),
|
||||
Self::MathAlignPoint(v) => v.eval(vm).map(Value::Content),
|
||||
Self::MathDelimited(v) => v.eval(vm).map(Value::Content),
|
||||
Self::MathScript(v) => v.eval(vm).map(Value::Content),
|
||||
Self::MathFrac(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Ident(v) => v.eval(vm),
|
||||
Self::None(v) => v.eval(vm),
|
||||
Self::Auto(v) => v.eval(vm),
|
||||
@ -295,7 +296,6 @@ impl Eval for ast::Expr {
|
||||
Self::Str(v) => v.eval(vm),
|
||||
Self::Code(v) => v.eval(vm),
|
||||
Self::Content(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Math(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Array(v) => v.eval(vm).map(Value::Array),
|
||||
Self::Dict(v) => v.eval(vm).map(Value::Dict),
|
||||
Self::Parenthesized(v) => v.eval(vm),
|
||||
@ -316,29 +316,12 @@ impl Eval for ast::Expr {
|
||||
Self::Break(v) => v.eval(vm),
|
||||
Self::Continue(v) => v.eval(vm),
|
||||
Self::Return(v) => v.eval(vm),
|
||||
}
|
||||
}
|
||||
}
|
||||
}?
|
||||
.spanned(span);
|
||||
|
||||
impl ast::Expr {
|
||||
fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
|
||||
Ok(match self {
|
||||
Self::Escape(v) => v.eval_in_math(vm)?,
|
||||
Self::Shorthand(v) => v.eval_in_math(vm)?,
|
||||
Self::Symbol(v) => v.eval_in_math(vm)?,
|
||||
Self::Ident(v) => v.eval_in_math(vm)?,
|
||||
Self::FuncCall(v) => v.eval_in_math(vm)?,
|
||||
_ => self.eval(vm)?.display_in_math(),
|
||||
}
|
||||
.spanned(self.span()))
|
||||
}
|
||||
|
||||
fn eval_without_parens(&self, vm: &mut Vm) -> SourceResult<Content> {
|
||||
Ok(match self {
|
||||
Self::Delimited(v) => v.eval_without_parens(vm)?,
|
||||
_ => self.eval_in_math(vm)?,
|
||||
}
|
||||
.spanned(self.span()))
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
|
||||
@ -375,44 +358,22 @@ impl Eval for ast::Parbreak {
|
||||
}
|
||||
|
||||
impl Eval for ast::Escape {
|
||||
type Output = Content;
|
||||
type Output = Value;
|
||||
|
||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
Ok((vm.items.text)(self.get().into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ast::Escape {
|
||||
fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
|
||||
Ok((vm.items.math_atom)(self.get().into()))
|
||||
fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||
// This can be in markup and math, going through a string ensure
|
||||
// that either text or atom is picked.
|
||||
Ok(Value::Str(self.get().into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Shorthand {
|
||||
type Output = Content;
|
||||
type Output = Value;
|
||||
|
||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
Ok((vm.items.text)(self.get().into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ast::Shorthand {
|
||||
fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
|
||||
Ok((vm.items.math_atom)(self.get().into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Symbol {
|
||||
type Output = Content;
|
||||
|
||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
Ok((vm.items.symbol)(self.get().into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ast::Symbol {
|
||||
fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
|
||||
Ok((vm.items.symbol)(EcoString::from(self.get()) + ":op:square".into()))
|
||||
fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||
// This can be in markup and math, going through a string ensure
|
||||
// that either text or atom is picked.
|
||||
Ok(Value::Str(self.get().into()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -513,20 +474,29 @@ impl Eval for ast::TermItem {
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Formula {
|
||||
type Output = Content;
|
||||
|
||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let body = self.body().eval(vm)?;
|
||||
let block = self.block();
|
||||
Ok((vm.items.formula)(body, block))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Math {
|
||||
type Output = Content;
|
||||
|
||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let seq = self
|
||||
.exprs()
|
||||
.map(|expr| expr.eval_in_math(vm))
|
||||
.collect::<SourceResult<_>>()?;
|
||||
let block = self.block();
|
||||
Ok((vm.items.math_formula)(Content::sequence(seq), block))
|
||||
Ok(Content::sequence(
|
||||
self.exprs()
|
||||
.map(|expr| Ok(expr.eval(vm)?.display_in_math()))
|
||||
.collect::<SourceResult<_>>()?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Atom {
|
||||
impl Eval for ast::MathAtom {
|
||||
type Output = Content;
|
||||
|
||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
@ -534,59 +504,15 @@ impl Eval for ast::Atom {
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Delimited {
|
||||
type Output = Content;
|
||||
impl Eval for ast::MathIdent {
|
||||
type Output = Value;
|
||||
|
||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let seq = self
|
||||
.exprs()
|
||||
.map(|expr| expr.eval_in_math(vm))
|
||||
.collect::<SourceResult<_>>()?;
|
||||
Ok((vm.items.math_delimited)(Content::sequence(seq)))
|
||||
Ok(vm.scopes.get_in_math(self).cloned().at(self.span())?)
|
||||
}
|
||||
}
|
||||
|
||||
impl ast::Delimited {
|
||||
fn eval_without_parens(&self, vm: &mut Vm) -> SourceResult<Content> {
|
||||
let exprs: Vec<_> = self.exprs().collect();
|
||||
let mut slice = exprs.as_slice();
|
||||
if let (Some(ast::Expr::Atom(first)), Some(ast::Expr::Atom(last))) =
|
||||
(exprs.first(), exprs.last())
|
||||
{
|
||||
if first.get() == "(" && last.get() == ")" {
|
||||
slice = &exprs[1..exprs.len() - 1];
|
||||
}
|
||||
}
|
||||
let seq = slice
|
||||
.iter()
|
||||
.map(|expr| expr.eval_in_math(vm))
|
||||
.collect::<SourceResult<_>>()?;
|
||||
Ok((vm.items.math_delimited)(Content::sequence(seq)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Script {
|
||||
type Output = Content;
|
||||
|
||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let base = self.base().eval_in_math(vm)?;
|
||||
let sub = self.sub().map(|expr| expr.eval_without_parens(vm)).transpose()?;
|
||||
let sup = self.sup().map(|expr| expr.eval_without_parens(vm)).transpose()?;
|
||||
Ok((vm.items.math_script)(base, sub, sup))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Frac {
|
||||
type Output = Content;
|
||||
|
||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let num = self.num().eval_without_parens(vm)?;
|
||||
let denom = self.denom().eval_without_parens(vm)?;
|
||||
Ok((vm.items.math_frac)(num, denom))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::AlignPoint {
|
||||
impl Eval for ast::MathAlignPoint {
|
||||
type Output = Content;
|
||||
|
||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
@ -594,27 +520,49 @@ impl Eval for ast::AlignPoint {
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::MathDelimited {
|
||||
type Output = Content;
|
||||
|
||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let open = self.open().eval(vm)?;
|
||||
let body = self.body().eval(vm)?;
|
||||
let close = self.close().eval(vm)?;
|
||||
Ok((vm.items.math_delimited)(open, body, close))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::MathScript {
|
||||
type Output = Content;
|
||||
|
||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let base = self.base().eval(vm)?.display_in_math();
|
||||
let sub = self
|
||||
.sub()
|
||||
.map(|expr| expr.eval(vm).map(Value::display_in_math))
|
||||
.transpose()?;
|
||||
let sup = self
|
||||
.sup()
|
||||
.map(|expr| expr.eval(vm).map(Value::display_in_math))
|
||||
.transpose()?;
|
||||
Ok((vm.items.math_script)(base, sub, sup))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::MathFrac {
|
||||
type Output = Content;
|
||||
|
||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let num = self.num().eval(vm)?.display_in_math();
|
||||
let denom = self.denom().eval(vm)?.display_in_math();
|
||||
Ok((vm.items.math_frac)(num, denom))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Ident {
|
||||
type Output = Value;
|
||||
|
||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let value = vm.scopes.get(self).cloned().at(self.span())?;
|
||||
Ok(match value {
|
||||
Value::Func(func) => Value::Func(func.spanned(self.span())),
|
||||
value => value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ast::Ident {
|
||||
fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
|
||||
if self.as_untyped().len() == self.len()
|
||||
&& matches!(vm.scopes.get_in_math(self), Ok(Value::Func(_)) | Err(_))
|
||||
{
|
||||
Ok((vm.items.symbol)(EcoString::from(self.get()) + ":op".into()))
|
||||
} else {
|
||||
Ok(vm.scopes.get_in_math(self).at(self.span())?.clone().display_in_math())
|
||||
}
|
||||
Ok(vm.scopes.get(self).cloned().at(self.span())?)
|
||||
}
|
||||
}
|
||||
|
||||
@ -686,12 +634,20 @@ impl Eval for ast::CodeBlock {
|
||||
|
||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
vm.scopes.enter();
|
||||
let output = eval_code(vm, &mut self.exprs())?;
|
||||
let output = self.body().eval(vm)?;
|
||||
vm.scopes.exit();
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Code {
|
||||
type Output = Value;
|
||||
|
||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
eval_code(vm, &mut self.exprs())
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate a stream of expressions.
|
||||
fn eval_code(
|
||||
vm: &mut Vm,
|
||||
@ -901,23 +857,9 @@ impl Eval for ast::FieldAccess {
|
||||
type Output = Value;
|
||||
|
||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let object = self.target().eval(vm)?;
|
||||
let span = self.field().span();
|
||||
let field = self.field().take();
|
||||
|
||||
Ok(match object {
|
||||
Value::Dict(dict) => dict.at(&field).at(span)?.clone(),
|
||||
Value::Content(content) => content
|
||||
.field(&field)
|
||||
.ok_or_else(|| format!("unknown field `{field}`"))
|
||||
.at(span)?,
|
||||
Value::Module(module) => module.get(&field).cloned().at(span)?,
|
||||
v => bail!(
|
||||
self.target().span(),
|
||||
"expected dictionary or content, found {}",
|
||||
v.type_name()
|
||||
),
|
||||
})
|
||||
let value = self.target().eval(vm)?;
|
||||
let field = self.field();
|
||||
value.field(&field).at(field.span())
|
||||
}
|
||||
}
|
||||
|
||||
@ -926,27 +868,13 @@ impl Eval for ast::FuncCall {
|
||||
|
||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let callee = self.callee();
|
||||
let callee = callee.eval(vm)?.cast::<Func>().at(callee.span())?;
|
||||
let args = self.args().eval(vm)?;
|
||||
Self::eval_call(vm, &callee, args, self.span())
|
||||
}
|
||||
}
|
||||
let callee_span = callee.span();
|
||||
let in_math = matches!(callee, ast::Expr::MathIdent(_));
|
||||
let callee = callee.eval(vm)?;
|
||||
let mut args = self.args().eval(vm)?;
|
||||
|
||||
impl ast::FuncCall {
|
||||
fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
|
||||
let callee = match self.callee() {
|
||||
ast::Expr::Ident(ident) => {
|
||||
vm.scopes.get_in_math(&ident).at(ident.span())?.clone()
|
||||
}
|
||||
expr => expr.eval(vm)?,
|
||||
};
|
||||
|
||||
if let Value::Func(callee) = callee {
|
||||
let args = self.args().eval(vm)?;
|
||||
Ok(Self::eval_call(vm, &callee, args, self.span())?.display_in_math())
|
||||
} else {
|
||||
if in_math && !matches!(callee, Value::Func(_)) {
|
||||
let mut body = (vm.items.math_atom)('('.into());
|
||||
let mut args = self.args().eval(vm)?;
|
||||
for (i, arg) in args.all::<Content>()?.into_iter().enumerate() {
|
||||
if i > 0 {
|
||||
body += (vm.items.math_atom)(','.into());
|
||||
@ -954,25 +882,28 @@ impl ast::FuncCall {
|
||||
body += arg;
|
||||
}
|
||||
body += (vm.items.math_atom)(')'.into());
|
||||
Ok(callee.display_in_math() + body)
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_call(
|
||||
vm: &mut Vm,
|
||||
callee: &Func,
|
||||
args: Args,
|
||||
span: Span,
|
||||
) -> SourceResult<Value> {
|
||||
if vm.depth >= MAX_CALL_DEPTH {
|
||||
bail!(span, "maximum function call depth exceeded");
|
||||
return Ok(Value::Content(callee.display_in_math() + body));
|
||||
}
|
||||
|
||||
let point = || Tracepoint::Call(callee.name().map(Into::into));
|
||||
callee.call(vm, args).trace(vm.world, point, span)
|
||||
let callee = callee.cast::<Func>().at(callee_span)?;
|
||||
complete_call(vm, &callee, args, self.span())
|
||||
}
|
||||
}
|
||||
|
||||
fn complete_call(
|
||||
vm: &mut Vm,
|
||||
callee: &Func,
|
||||
args: Args,
|
||||
span: Span,
|
||||
) -> SourceResult<Value> {
|
||||
if vm.depth >= MAX_CALL_DEPTH {
|
||||
bail!(span, "maximum function call depth exceeded");
|
||||
}
|
||||
|
||||
let point = || Tracepoint::Call(callee.name().map(Into::into));
|
||||
callee.call(vm, args).trace(vm.world, point, span)
|
||||
}
|
||||
|
||||
impl Eval for ast::MethodCall {
|
||||
type Output = Value;
|
||||
|
||||
@ -988,7 +919,7 @@ impl Eval for ast::MethodCall {
|
||||
if let Value::Func(callee) =
|
||||
module.get(&method).cloned().at(method.span())?
|
||||
{
|
||||
return ast::FuncCall::eval_call(vm, &callee, args, self.span());
|
||||
return complete_call(vm, &callee, args, self.span());
|
||||
}
|
||||
}
|
||||
|
||||
@ -999,7 +930,7 @@ impl Eval for ast::MethodCall {
|
||||
|
||||
if let Value::Module(module) = &value {
|
||||
if let Value::Func(callee) = module.get(&method).at(method.span())? {
|
||||
return ast::FuncCall::eval_call(vm, callee, args, self.span());
|
||||
return complete_call(vm, callee, args, self.span());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1367,7 +1298,7 @@ impl Eval for ast::ModuleInclude {
|
||||
}
|
||||
|
||||
/// Process an import of a module relative to the current location.
|
||||
fn import(vm: &Vm, source: Value, span: Span) -> SourceResult<Module> {
|
||||
fn import(vm: &mut Vm, source: Value, span: Span) -> SourceResult<Module> {
|
||||
let path = match source {
|
||||
Value::Str(path) => path,
|
||||
Value::Module(module) => return Ok(module),
|
||||
@ -1386,7 +1317,8 @@ fn import(vm: &Vm, source: Value, span: Span) -> SourceResult<Module> {
|
||||
// Evaluate the file.
|
||||
let source = vm.world.source(id);
|
||||
let point = || Tracepoint::Import;
|
||||
eval(vm.world, vm.route, source).trace(vm.world, point, span)
|
||||
eval(vm.world, vm.route, TrackedMut::reborrow_mut(&mut vm.tracer), source)
|
||||
.trace(vm.world, point, span)
|
||||
}
|
||||
|
||||
impl Eval for ast::LoopBreak {
|
||||
@ -1446,7 +1378,12 @@ impl Access for ast::Expr {
|
||||
|
||||
impl Access for ast::Ident {
|
||||
fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
|
||||
vm.scopes.get_mut(self).at(self.span())
|
||||
let span = self.span();
|
||||
let value = vm.scopes.get_mut(self).at(span)?;
|
||||
if vm.traced == Some(span) {
|
||||
vm.tracer.trace(value.clone());
|
||||
}
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -505,17 +505,17 @@ mod tests {
|
||||
fn test_captures() {
|
||||
// Let binding and function definition.
|
||||
test("#let x = x", &["x"]);
|
||||
test("#let x; {x + y}", &["y"]);
|
||||
test("#let x; #{x + y}", &["y"]);
|
||||
test("#let f(x, y) = x + y", &[]);
|
||||
test("#let f(x, y) = f", &[]);
|
||||
test("#let f = (x, y) => f", &["f"]);
|
||||
|
||||
// Closure with different kinds of params.
|
||||
test("{(x, y) => x + z}", &["z"]);
|
||||
test("{(x: y, z) => x + z}", &["y"]);
|
||||
test("{(..x) => x + y}", &["y"]);
|
||||
test("{(x, y: x + z) => x + y}", &["x", "z"]);
|
||||
test("{x => x; x}", &["x"]);
|
||||
test("#{(x, y) => x + z}", &["z"]);
|
||||
test("#{(x: y, z) => x + z}", &["y"]);
|
||||
test("#{(..x) => x + y}", &["y"]);
|
||||
test("#{(x, y: x + z) => x + y}", &["x", "z"]);
|
||||
test("#{x => x; x}", &["x"]);
|
||||
|
||||
// Show rule.
|
||||
test("#show y: x => x", &["y"]);
|
||||
@ -532,7 +532,7 @@ mod tests {
|
||||
test("#import x + y: x, y, z", &["x", "y"]);
|
||||
|
||||
// Blocks.
|
||||
test("{ let x = 1; { let y = 2; y }; x + y }", &["y"]);
|
||||
test("[#let x = 1]#x", &["x"]);
|
||||
test("#{ let x = 1; { let y = 2; y }; x + y }", &["y"]);
|
||||
test("#[#let x = 1]#x", &["x"]);
|
||||
}
|
||||
}
|
||||
|
@ -68,19 +68,19 @@ pub struct LangItems {
|
||||
/// An item in a term list: `/ Term: Details`.
|
||||
pub term_item: fn(term: Content, description: Content) -> Content,
|
||||
/// A mathematical formula: `$x$`, `$ x^2 $`.
|
||||
pub math_formula: fn(body: Content, block: bool) -> Content,
|
||||
/// A subsection in a math formula that is surrounded by matched delimiters:
|
||||
/// `[x + y]`.
|
||||
pub math_delimited: fn(body: Content) -> Content,
|
||||
pub formula: fn(body: Content, block: bool) -> Content,
|
||||
/// An atom in a formula: `x`, `+`, `12`.
|
||||
pub math_atom: fn(atom: EcoString) -> Content,
|
||||
/// An alignment point in a formula: `&`.
|
||||
pub math_align_point: fn() -> Content,
|
||||
/// A subsection in a math formula that is surrounded by matched delimiters:
|
||||
/// `[x + y]`.
|
||||
pub math_delimited: fn(open: Content, body: Content, close: Content) -> Content,
|
||||
/// A base with optional sub- and superscripts in a formula: `a_1^2`.
|
||||
pub math_script:
|
||||
fn(base: Content, sub: Option<Content>, sup: Option<Content>) -> Content,
|
||||
/// A fraction in a formula: `x/2`.
|
||||
pub math_frac: fn(num: Content, denom: Content) -> Content,
|
||||
/// An alignment point in a formula: `&`.
|
||||
pub math_align_point: fn() -> Content,
|
||||
}
|
||||
|
||||
impl Debug for LangItems {
|
||||
@ -108,7 +108,7 @@ impl Hash for LangItems {
|
||||
self.list_item.hash(state);
|
||||
self.enum_item.hash(state);
|
||||
self.term_item.hash(state);
|
||||
self.math_formula.hash(state);
|
||||
self.formula.hash(state);
|
||||
self.math_atom.hash(state);
|
||||
self.math_script.hash(state);
|
||||
self.math_frac.hash(state);
|
||||
|
@ -29,7 +29,7 @@ pub trait AstNode: Sized {
|
||||
|
||||
macro_rules! node {
|
||||
($(#[$attr:meta])* $name:ident) => {
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Hash)]
|
||||
#[repr(transparent)]
|
||||
$(#[$attr])*
|
||||
pub struct $name(SyntaxNode);
|
||||
@ -114,18 +114,22 @@ pub enum Expr {
|
||||
/// An item in a term list: `/ Term: Details`.
|
||||
Term(TermItem),
|
||||
/// A math formula: `$x$`, `$ x^2 $`.
|
||||
Formula(Formula),
|
||||
/// A math formula: `$x$`, `$ x^2 $`.
|
||||
Math(Math),
|
||||
/// An atom in a math formula: `x`, `+`, `12`.
|
||||
Atom(Atom),
|
||||
MathAtom(MathAtom),
|
||||
/// An identifier in a math formula: `pi`.
|
||||
MathIdent(MathIdent),
|
||||
/// An alignment point in a math formula: `&`.
|
||||
MathAlignPoint(MathAlignPoint),
|
||||
/// A subsection in a math formula that is surrounded by matched delimiters:
|
||||
/// `[x + y]`.
|
||||
Delimited(Delimited),
|
||||
MathDelimited(MathDelimited),
|
||||
/// A base with optional sub- and superscripts in a math formula: `a_1^2`.
|
||||
Script(Script),
|
||||
MathScript(MathScript),
|
||||
/// A fraction in a math formula: `x/2`.
|
||||
Frac(Frac),
|
||||
/// An alignment point in a math formula: `&`.
|
||||
AlignPoint(AlignPoint),
|
||||
MathFrac(MathFrac),
|
||||
/// An identifier: `left`.
|
||||
Ident(Ident),
|
||||
/// The `none` literal.
|
||||
@ -205,7 +209,6 @@ impl AstNode for Expr {
|
||||
SyntaxKind::Text => node.cast().map(Self::Text),
|
||||
SyntaxKind::Escape => node.cast().map(Self::Escape),
|
||||
SyntaxKind::Shorthand => node.cast().map(Self::Shorthand),
|
||||
SyntaxKind::Symbol => node.cast().map(Self::Symbol),
|
||||
SyntaxKind::SmartQuote => node.cast().map(Self::SmartQuote),
|
||||
SyntaxKind::Strong => node.cast().map(Self::Strong),
|
||||
SyntaxKind::Emph => node.cast().map(Self::Emph),
|
||||
@ -217,12 +220,14 @@ impl AstNode for Expr {
|
||||
SyntaxKind::ListItem => node.cast().map(Self::List),
|
||||
SyntaxKind::EnumItem => node.cast().map(Self::Enum),
|
||||
SyntaxKind::TermItem => node.cast().map(Self::Term),
|
||||
SyntaxKind::Formula => node.cast().map(Self::Formula),
|
||||
SyntaxKind::Math => node.cast().map(Self::Math),
|
||||
SyntaxKind::Atom => node.cast().map(Self::Atom),
|
||||
SyntaxKind::Delimited => node.cast().map(Self::Delimited),
|
||||
SyntaxKind::Script => node.cast().map(Self::Script),
|
||||
SyntaxKind::Frac => node.cast().map(Self::Frac),
|
||||
SyntaxKind::AlignPoint => node.cast().map(Self::AlignPoint),
|
||||
SyntaxKind::MathAtom => node.cast().map(Self::MathAtom),
|
||||
SyntaxKind::MathIdent => node.cast().map(Self::MathIdent),
|
||||
SyntaxKind::MathAlignPoint => node.cast().map(Self::MathAlignPoint),
|
||||
SyntaxKind::MathDelimited => node.cast().map(Self::MathDelimited),
|
||||
SyntaxKind::MathScript => node.cast().map(Self::MathScript),
|
||||
SyntaxKind::MathFrac => node.cast().map(Self::MathFrac),
|
||||
SyntaxKind::Ident => node.cast().map(Self::Ident),
|
||||
SyntaxKind::None => node.cast().map(Self::None),
|
||||
SyntaxKind::Auto => node.cast().map(Self::Auto),
|
||||
@ -265,7 +270,6 @@ impl AstNode for Expr {
|
||||
Self::Parbreak(v) => v.as_untyped(),
|
||||
Self::Escape(v) => v.as_untyped(),
|
||||
Self::Shorthand(v) => v.as_untyped(),
|
||||
Self::Symbol(v) => v.as_untyped(),
|
||||
Self::SmartQuote(v) => v.as_untyped(),
|
||||
Self::Strong(v) => v.as_untyped(),
|
||||
Self::Emph(v) => v.as_untyped(),
|
||||
@ -277,12 +281,14 @@ impl AstNode for Expr {
|
||||
Self::List(v) => v.as_untyped(),
|
||||
Self::Enum(v) => v.as_untyped(),
|
||||
Self::Term(v) => v.as_untyped(),
|
||||
Self::Formula(v) => v.as_untyped(),
|
||||
Self::Math(v) => v.as_untyped(),
|
||||
Self::Atom(v) => v.as_untyped(),
|
||||
Self::Delimited(v) => v.as_untyped(),
|
||||
Self::Script(v) => v.as_untyped(),
|
||||
Self::Frac(v) => v.as_untyped(),
|
||||
Self::AlignPoint(v) => v.as_untyped(),
|
||||
Self::MathAtom(v) => v.as_untyped(),
|
||||
Self::MathIdent(v) => v.as_untyped(),
|
||||
Self::MathAlignPoint(v) => v.as_untyped(),
|
||||
Self::MathDelimited(v) => v.as_untyped(),
|
||||
Self::MathScript(v) => v.as_untyped(),
|
||||
Self::MathFrac(v) => v.as_untyped(),
|
||||
Self::Ident(v) => v.as_untyped(),
|
||||
Self::None(v) => v.as_untyped(),
|
||||
Self::Auto(v) => v.as_untyped(),
|
||||
@ -317,6 +323,12 @@ impl AstNode for Expr {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Expr {
|
||||
fn default() -> Self {
|
||||
Expr::Space(Space::default())
|
||||
}
|
||||
}
|
||||
|
||||
node! {
|
||||
/// Plain text without markup.
|
||||
Text
|
||||
@ -360,9 +372,9 @@ impl Escape {
|
||||
u32::from_str_radix(hex, 16)
|
||||
.ok()
|
||||
.and_then(std::char::from_u32)
|
||||
.expect("unicode escape is invalid")
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
s.eat().expect("escape is missing escaped character")
|
||||
s.eat().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -378,10 +390,11 @@ impl Shorthand {
|
||||
pub fn get(&self) -> char {
|
||||
match self.0.text().as_str() {
|
||||
"~" => '\u{00A0}',
|
||||
"..." => '\u{2026}',
|
||||
"--" => '\u{2013}',
|
||||
"---" => '\u{2014}',
|
||||
"-?" => '\u{00AD}',
|
||||
"..." => '…',
|
||||
"*" => '∗',
|
||||
"!=" => '≠',
|
||||
"<=" => '≤',
|
||||
">=" => '≥',
|
||||
@ -432,7 +445,7 @@ node! {
|
||||
impl Strong {
|
||||
/// The contents of the strong node.
|
||||
pub fn body(&self) -> Markup {
|
||||
self.0.cast_first_match().expect("strong emphasis is missing body")
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@ -444,7 +457,7 @@ node! {
|
||||
impl Emph {
|
||||
/// The contents of the emphasis node.
|
||||
pub fn body(&self) -> Markup {
|
||||
self.0.cast_first_match().expect("emphasis is missing body")
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@ -568,7 +581,7 @@ node! {
|
||||
impl Heading {
|
||||
/// The contents of the heading.
|
||||
pub fn body(&self) -> Markup {
|
||||
self.0.cast_first_match().expect("heading is missing markup body")
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The section depth (numer of equals signs).
|
||||
@ -577,7 +590,7 @@ impl Heading {
|
||||
.children()
|
||||
.find(|node| node.kind() == SyntaxKind::HeadingMarker)
|
||||
.and_then(|node| node.len().try_into().ok())
|
||||
.expect("heading is missing marker")
|
||||
.unwrap_or(NonZeroUsize::new(1).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
@ -589,7 +602,7 @@ node! {
|
||||
impl ListItem {
|
||||
/// The contents of the list item.
|
||||
pub fn body(&self) -> Markup {
|
||||
self.0.cast_first_match().expect("list item is missing body")
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@ -609,7 +622,7 @@ impl EnumItem {
|
||||
|
||||
/// The contents of the list item.
|
||||
pub fn body(&self) -> Markup {
|
||||
self.0.cast_first_match().expect("enum item is missing body")
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@ -621,41 +634,53 @@ node! {
|
||||
impl TermItem {
|
||||
/// The term described by the item.
|
||||
pub fn term(&self) -> Markup {
|
||||
self.0.cast_first_match().expect("term list item is missing term")
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The description of the term.
|
||||
pub fn description(&self) -> Markup {
|
||||
self.0
|
||||
.cast_last_match()
|
||||
.expect("term list item is missing description")
|
||||
self.0.cast_last_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
node! {
|
||||
/// A math formula: `$x$`, `$ x^2 $`.
|
||||
Math
|
||||
Formula
|
||||
}
|
||||
|
||||
impl Math {
|
||||
/// The expressions the formula consists of.
|
||||
pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ {
|
||||
self.0.children().filter_map(Expr::cast_with_space)
|
||||
impl Formula {
|
||||
/// The contained math.
|
||||
pub fn body(&self) -> Math {
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Whether the formula should be displayed as a separate block.
|
||||
pub fn block(&self) -> bool {
|
||||
matches!(self.exprs().next(), Some(Expr::Space(_)))
|
||||
&& matches!(self.exprs().last(), Some(Expr::Space(_)))
|
||||
let is_space = |node: Option<&SyntaxNode>| {
|
||||
node.map(SyntaxNode::kind) == Some(SyntaxKind::Space)
|
||||
};
|
||||
is_space(self.0.children().nth(1)) && is_space(self.0.children().nth_back(1))
|
||||
}
|
||||
}
|
||||
|
||||
node! {
|
||||
/// Math markup.
|
||||
Math
|
||||
}
|
||||
|
||||
impl Math {
|
||||
/// The expressions the mathematical content consists of.
|
||||
pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ {
|
||||
self.0.children().filter_map(Expr::cast_with_space)
|
||||
}
|
||||
}
|
||||
|
||||
node! {
|
||||
/// A atom in a formula: `x`, `+`, `12`.
|
||||
Atom
|
||||
MathAtom
|
||||
}
|
||||
|
||||
impl Atom {
|
||||
impl MathAtom {
|
||||
/// Get the atom's text.
|
||||
pub fn get(&self) -> &EcoString {
|
||||
self.0.text()
|
||||
@ -663,27 +688,72 @@ impl Atom {
|
||||
}
|
||||
|
||||
node! {
|
||||
/// A subsection in a math formula that is surrounded by matched delimiters:
|
||||
/// `[x + y]`.
|
||||
Delimited
|
||||
/// An identifier in a math formula: `pi`.
|
||||
MathIdent
|
||||
}
|
||||
|
||||
impl Delimited {
|
||||
impl MathIdent {
|
||||
/// Get the identifier.
|
||||
pub fn get(&self) -> &EcoString {
|
||||
self.0.text()
|
||||
}
|
||||
|
||||
/// Take out the contained identifier.
|
||||
pub fn take(self) -> EcoString {
|
||||
self.0.into_text()
|
||||
}
|
||||
|
||||
/// Get the identifier as a string slice.
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for MathIdent {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
node! {
|
||||
/// An alignment point in a formula: `&`.
|
||||
MathAlignPoint
|
||||
}
|
||||
|
||||
node! {
|
||||
/// A subsection in a math formula that is surrounded by matched delimiters:
|
||||
/// `[x + y]`.
|
||||
MathDelimited
|
||||
}
|
||||
|
||||
impl MathDelimited {
|
||||
/// The opening delimiter.
|
||||
pub fn open(&self) -> MathAtom {
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The contents, including the delimiters.
|
||||
pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ {
|
||||
self.0.children().filter_map(Expr::cast_with_space)
|
||||
pub fn body(&self) -> Math {
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The closing delimiter.
|
||||
pub fn close(&self) -> MathAtom {
|
||||
self.0.cast_last_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
node! {
|
||||
/// A base with an optional sub- and superscript in a formula: `a_1^2`.
|
||||
Script
|
||||
MathScript
|
||||
}
|
||||
|
||||
impl Script {
|
||||
impl MathScript {
|
||||
/// The base of the script.
|
||||
pub fn base(&self) -> Expr {
|
||||
self.0.cast_first_match().expect("script node is missing base")
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The subscript.
|
||||
@ -705,26 +775,21 @@ impl Script {
|
||||
|
||||
node! {
|
||||
/// A fraction in a formula: `x/2`
|
||||
Frac
|
||||
MathFrac
|
||||
}
|
||||
|
||||
impl Frac {
|
||||
impl MathFrac {
|
||||
/// The numerator.
|
||||
pub fn num(&self) -> Expr {
|
||||
self.0.cast_first_match().expect("fraction is missing numerator")
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The denominator.
|
||||
pub fn denom(&self) -> Expr {
|
||||
self.0.cast_last_match().expect("fraction is missing denominator")
|
||||
self.0.cast_last_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
node! {
|
||||
/// An alignment point in a formula: `&`.
|
||||
AlignPoint
|
||||
}
|
||||
|
||||
node! {
|
||||
/// An identifier: `it`.
|
||||
Ident
|
||||
@ -732,17 +797,13 @@ node! {
|
||||
|
||||
impl Ident {
|
||||
/// Get the identifier.
|
||||
pub fn get(&self) -> &str {
|
||||
self.0.text().trim_start_matches('#')
|
||||
pub fn get(&self) -> &EcoString {
|
||||
self.0.text()
|
||||
}
|
||||
|
||||
/// Take out the contained identifier.
|
||||
pub fn take(self) -> EcoString {
|
||||
let text = self.0.into_text();
|
||||
match text.strip_prefix('#') {
|
||||
Some(text) => text.into(),
|
||||
Option::None => text,
|
||||
}
|
||||
self.0.into_text()
|
||||
}
|
||||
|
||||
/// Get the identifier as a string slice.
|
||||
@ -789,7 +850,7 @@ node! {
|
||||
impl Int {
|
||||
/// Get the integer value.
|
||||
pub fn get(&self) -> i64 {
|
||||
self.0.text().parse().expect("integer is invalid")
|
||||
self.0.text().parse().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@ -801,7 +862,7 @@ node! {
|
||||
impl Float {
|
||||
/// Get the floating-point value.
|
||||
pub fn get(&self) -> f64 {
|
||||
self.0.text().parse().expect("float is invalid")
|
||||
self.0.text().parse().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@ -821,7 +882,7 @@ impl Numeric {
|
||||
.count();
|
||||
|
||||
let split = text.len() - count;
|
||||
let value = text[..split].parse().expect("number is invalid");
|
||||
let value = text[..split].parse().unwrap_or_default();
|
||||
let unit = match &text[split..] {
|
||||
"pt" => Unit::Length(AbsUnit::Pt),
|
||||
"mm" => Unit::Length(AbsUnit::Mm),
|
||||
@ -910,7 +971,19 @@ node! {
|
||||
}
|
||||
|
||||
impl CodeBlock {
|
||||
/// The list of expressions contained in the block.
|
||||
/// The contained code.
|
||||
pub fn body(&self) -> Code {
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
node! {
|
||||
/// Code.
|
||||
Code
|
||||
}
|
||||
|
||||
impl Code {
|
||||
/// The list of expressions contained in the code.
|
||||
pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ {
|
||||
self.0.children().filter_map(SyntaxNode::cast)
|
||||
}
|
||||
@ -924,7 +997,7 @@ node! {
|
||||
impl ContentBlock {
|
||||
/// The contained markup.
|
||||
pub fn body(&self) -> Markup {
|
||||
self.0.cast_first_match().expect("content block is missing body")
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@ -936,9 +1009,7 @@ node! {
|
||||
impl Parenthesized {
|
||||
/// The wrapped expression.
|
||||
pub fn expr(&self) -> Expr {
|
||||
self.0
|
||||
.cast_first_match()
|
||||
.expect("parenthesized expression is missing expression")
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1029,12 +1100,12 @@ node! {
|
||||
impl Named {
|
||||
/// The name: `thickness`.
|
||||
pub fn name(&self) -> Ident {
|
||||
self.0.cast_first_match().expect("named pair is missing name")
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The right-hand side of the pair: `3pt`.
|
||||
pub fn expr(&self) -> Expr {
|
||||
self.0.cast_last_match().expect("named pair is missing expression")
|
||||
self.0.cast_last_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1049,12 +1120,12 @@ impl Keyed {
|
||||
self.0
|
||||
.children()
|
||||
.find_map(|node| node.cast::<Str>())
|
||||
.expect("keyed pair is missing key")
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The right-hand side of the pair: `true`.
|
||||
pub fn expr(&self) -> Expr {
|
||||
self.0.cast_last_match().expect("keyed pair is missing expression")
|
||||
self.0.cast_last_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1069,12 +1140,12 @@ impl Unary {
|
||||
self.0
|
||||
.children()
|
||||
.find_map(|node| UnOp::from_kind(node.kind()))
|
||||
.expect("unary operation is missing operator")
|
||||
.unwrap_or(UnOp::Pos)
|
||||
}
|
||||
|
||||
/// The expression to operate on: `x`.
|
||||
pub fn expr(&self) -> Expr {
|
||||
self.0.cast_last_match().expect("unary operation is missing child")
|
||||
self.0.cast_last_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1137,21 +1208,17 @@ impl Binary {
|
||||
SyntaxKind::In if not => Some(BinOp::NotIn),
|
||||
_ => BinOp::from_kind(node.kind()),
|
||||
})
|
||||
.expect("binary operation is missing operator")
|
||||
.unwrap_or(BinOp::Add)
|
||||
}
|
||||
|
||||
/// The left-hand side of the operation: `a`.
|
||||
pub fn lhs(&self) -> Expr {
|
||||
self.0
|
||||
.cast_first_match()
|
||||
.expect("binary operation is missing left-hand side")
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The right-hand side of the operation: `b`.
|
||||
pub fn rhs(&self) -> Expr {
|
||||
self.0
|
||||
.cast_last_match()
|
||||
.expect("binary operation is missing right-hand side")
|
||||
self.0.cast_last_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1317,12 +1384,12 @@ node! {
|
||||
impl FieldAccess {
|
||||
/// The expression to access the field on.
|
||||
pub fn target(&self) -> Expr {
|
||||
self.0.cast_first_match().expect("field access is missing object")
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The name of the field.
|
||||
pub fn field(&self) -> Ident {
|
||||
self.0.cast_last_match().expect("field access is missing name")
|
||||
self.0.cast_last_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1334,14 +1401,12 @@ node! {
|
||||
impl FuncCall {
|
||||
/// The function to call.
|
||||
pub fn callee(&self) -> Expr {
|
||||
self.0.cast_first_match().expect("function call is missing callee")
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The arguments to the function.
|
||||
pub fn args(&self) -> Args {
|
||||
self.0
|
||||
.cast_last_match()
|
||||
.expect("function call is missing argument list")
|
||||
self.0.cast_last_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1353,19 +1418,17 @@ node! {
|
||||
impl MethodCall {
|
||||
/// The expression to call the method on.
|
||||
pub fn target(&self) -> Expr {
|
||||
self.0.cast_first_match().expect("method call is missing target")
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The name of the method.
|
||||
pub fn method(&self) -> Ident {
|
||||
self.0.cast_last_match().expect("method call is missing name")
|
||||
self.0.cast_last_match().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The arguments to the method.
|
||||
pub fn args(&self) -> Args {
|
||||
self.0
|
||||
.cast_last_match()
|
||||
.expect("method call is missing argument list")
|
||||
self.0.cast_last_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1428,14 +1491,13 @@ impl Closure {
|
||||
self.0
|
||||
.children()
|
||||
.find(|x| x.kind() == SyntaxKind::Params)
|
||||
.expect("closure is missing parameter list")
|
||||
.children()
|
||||
.map_or([].iter(), |params| params.children())
|
||||
.filter_map(SyntaxNode::cast)
|
||||
}
|
||||
|
||||
/// The body of the closure.
|
||||
pub fn body(&self) -> Expr {
|
||||
self.0.cast_last_match().expect("closure is missing body")
|
||||
self.0.cast_last_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1479,10 +1541,8 @@ impl LetBinding {
|
||||
pub fn binding(&self) -> Ident {
|
||||
match self.0.cast_first_match() {
|
||||
Some(Expr::Ident(binding)) => binding,
|
||||
Some(Expr::Closure(closure)) => {
|
||||
closure.name().expect("let-bound closure is missing name")
|
||||
}
|
||||
_ => panic!("let is missing binding"),
|
||||
Some(Expr::Closure(closure)) => closure.name().unwrap_or_default(),
|
||||
_ => Ident::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1506,12 +1566,12 @@ node! {
|
||||
impl SetRule {
|
||||
/// The function to set style properties for.
|
||||
pub fn target(&self) -> Expr {
|
||||
self.0.cast_first_match().expect("set rule is missing target")
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The style properties to set.
|
||||
pub fn args(&self) -> Args {
|
||||
self.0.cast_last_match().expect("set rule is missing argument list")
|
||||
self.0.cast_last_match().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// A condition under which the set rule applies.
|
||||
@ -1540,7 +1600,7 @@ impl ShowRule {
|
||||
|
||||
/// The transformation recipe.
|
||||
pub fn transform(&self) -> Expr {
|
||||
self.0.cast_last_match().expect("show rule is missing transform")
|
||||
self.0.cast_last_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1552,7 +1612,7 @@ node! {
|
||||
impl Conditional {
|
||||
/// The condition which selects the body to evaluate.
|
||||
pub fn condition(&self) -> Expr {
|
||||
self.0.cast_first_match().expect("conditional is missing condition")
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The expression to evaluate if the condition is true.
|
||||
@ -1561,7 +1621,7 @@ impl Conditional {
|
||||
.children()
|
||||
.filter_map(SyntaxNode::cast)
|
||||
.nth(1)
|
||||
.expect("conditional is missing body")
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The expression to evaluate if the condition is false.
|
||||
@ -1578,12 +1638,12 @@ node! {
|
||||
impl WhileLoop {
|
||||
/// The condition which selects whether to evaluate the body.
|
||||
pub fn condition(&self) -> Expr {
|
||||
self.0.cast_first_match().expect("while loop is missing condition")
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The expression to evaluate while the condition is true.
|
||||
pub fn body(&self) -> Expr {
|
||||
self.0.cast_last_match().expect("while loop is missing body")
|
||||
self.0.cast_last_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1595,17 +1655,17 @@ node! {
|
||||
impl ForLoop {
|
||||
/// The pattern to assign to.
|
||||
pub fn pattern(&self) -> ForPattern {
|
||||
self.0.cast_first_match().expect("for loop is missing pattern")
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The expression to iterate over.
|
||||
pub fn iter(&self) -> Expr {
|
||||
self.0.cast_first_match().expect("for loop is missing iterable")
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The expression to evaluate for each iteration.
|
||||
pub fn body(&self) -> Expr {
|
||||
self.0.cast_last_match().expect("for loop is missing body")
|
||||
self.0.cast_last_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1628,7 +1688,7 @@ impl ForPattern {
|
||||
|
||||
/// The value part of the pattern.
|
||||
pub fn value(&self) -> Ident {
|
||||
self.0.cast_last_match().expect("for loop pattern is missing value")
|
||||
self.0.cast_last_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1640,7 +1700,7 @@ node! {
|
||||
impl ModuleImport {
|
||||
/// The module or path from which the items should be imported.
|
||||
pub fn source(&self) -> Expr {
|
||||
self.0.cast_last_match().expect("module import is missing source")
|
||||
self.0.cast_last_match().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The items to be imported.
|
||||
@ -1673,7 +1733,7 @@ node! {
|
||||
impl ModuleInclude {
|
||||
/// The module or path from which the content should be included.
|
||||
pub fn source(&self) -> Expr {
|
||||
self.0.cast_last_match().expect("module include is missing path")
|
||||
self.0.cast_last_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,19 +58,26 @@ pub enum SyntaxKind {
|
||||
/// Introduces a term item: `/`.
|
||||
TermMarker,
|
||||
/// A mathematical formula: `$x$`, `$ x^2 $`.
|
||||
Formula,
|
||||
|
||||
/// Mathematical markup.
|
||||
Math,
|
||||
/// An atom in math: `x`, `+`, `12`.
|
||||
Atom,
|
||||
MathAtom,
|
||||
/// An identifier in math: `pi`.
|
||||
MathIdent,
|
||||
/// An alignment point in math: `&`.
|
||||
MathAlignPoint,
|
||||
/// A subsection in a math formula that is surrounded by matched delimiters:
|
||||
/// `[x + y]`.
|
||||
Delimited,
|
||||
MathDelimited,
|
||||
/// A base with optional sub- and superscripts in math: `a_1^2`.
|
||||
Script,
|
||||
MathScript,
|
||||
/// A fraction in math: `x/2`.
|
||||
Frac,
|
||||
/// An alignment point in math: `&`.
|
||||
AlignPoint,
|
||||
MathFrac,
|
||||
|
||||
/// A hashtag that switches into code mode: `#`.
|
||||
Hashtag,
|
||||
/// A left curly brace, starting a code block: `{`.
|
||||
LeftBrace,
|
||||
/// A right curly brace, terminating a code block: `}`.
|
||||
@ -175,6 +182,8 @@ pub enum SyntaxKind {
|
||||
/// The `as` keyword.
|
||||
As,
|
||||
|
||||
/// Code.
|
||||
Code,
|
||||
/// An identifier: `it`.
|
||||
Ident,
|
||||
/// A boolean: `true`, `false`.
|
||||
@ -338,12 +347,15 @@ impl SyntaxKind {
|
||||
Self::EnumMarker => "enum marker",
|
||||
Self::TermItem => "term list item",
|
||||
Self::TermMarker => "term marker",
|
||||
Self::Math => "math formula",
|
||||
Self::Delimited => "delimited math",
|
||||
Self::Atom => "math atom",
|
||||
Self::Script => "script",
|
||||
Self::Frac => "fraction",
|
||||
Self::AlignPoint => "alignment point",
|
||||
Self::Formula => "math formula",
|
||||
Self::Math => "math",
|
||||
Self::MathIdent => "math identifier",
|
||||
Self::MathAtom => "math atom",
|
||||
Self::MathAlignPoint => "math alignment point",
|
||||
Self::MathDelimited => "delimited math",
|
||||
Self::MathScript => "math script",
|
||||
Self::MathFrac => "math fraction",
|
||||
Self::Hashtag => "hashtag",
|
||||
Self::LeftBrace => "opening brace",
|
||||
Self::RightBrace => "closing brace",
|
||||
Self::LeftBracket => "opening bracket",
|
||||
@ -394,6 +406,7 @@ impl SyntaxKind {
|
||||
Self::Import => "keyword `import`",
|
||||
Self::Include => "keyword `include`",
|
||||
Self::As => "keyword `as`",
|
||||
Self::Code => "code",
|
||||
Self::Ident => "identifier",
|
||||
Self::Bool => "boolean",
|
||||
Self::Int => "integer",
|
||||
|
@ -376,7 +376,7 @@ impl Lexer<'_> {
|
||||
Some('-') if !s.at(['-', '?']) => {}
|
||||
Some('.') if !s.at("..") => {}
|
||||
Some('h') if !s.at("ttp://") && !s.at("ttps://") => {}
|
||||
Some('@' | '#') if !s.at(is_id_start) => {}
|
||||
Some('@') if !s.at(is_id_start) => {}
|
||||
_ => break,
|
||||
}
|
||||
|
||||
@ -410,15 +410,8 @@ impl Lexer<'_> {
|
||||
'\\' => self.backslash(),
|
||||
':' if self.s.at(is_id_start) => self.maybe_symbol(),
|
||||
'"' => 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,
|
||||
@ -429,13 +422,17 @@ impl Lexer<'_> {
|
||||
'-' 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::Shorthand,
|
||||
|
||||
'#' if !self.s.at(char::is_whitespace) => SyntaxKind::Hashtag,
|
||||
'_' => SyntaxKind::Underscore,
|
||||
'$' => SyntaxKind::Dollar,
|
||||
'/' => SyntaxKind::Slash,
|
||||
'^' => SyntaxKind::Hat,
|
||||
'&' => SyntaxKind::AlignPoint,
|
||||
'&' => SyntaxKind::MathAlignPoint,
|
||||
|
||||
// Identifiers and symbol notation.
|
||||
c if is_math_id_start(c) && self.s.at(is_math_id_continue) => {
|
||||
@ -479,7 +476,7 @@ impl Lexer<'_> {
|
||||
.map_or(0, str::len);
|
||||
self.s.jump(start + len);
|
||||
}
|
||||
SyntaxKind::Atom
|
||||
SyntaxKind::MathAtom
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,6 +95,11 @@ impl SyntaxNode {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the node can be cast to the given AST node.
|
||||
pub fn is<T: AstNode>(&self) -> bool {
|
||||
self.cast::<T>().is_some()
|
||||
}
|
||||
|
||||
/// Try to convert the node to a typed AST node.
|
||||
pub fn cast<T: AstNode>(&self) -> Option<T> {
|
||||
T::from_untyped(self)
|
||||
@ -144,6 +149,16 @@ impl SyntaxNode {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the child to another kind.
|
||||
pub(super) fn convert_to_kind(&mut self, kind: SyntaxKind) {
|
||||
debug_assert!(!kind.is_error());
|
||||
match &mut self.0 {
|
||||
Repr::Leaf(leaf) => leaf.kind = kind,
|
||||
Repr::Inner(inner) => Arc::make_mut(inner).kind = kind,
|
||||
Repr::Error(_) => panic!("cannot convert error"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the child to an error.
|
||||
pub(super) fn convert_to_error(&mut self, message: impl Into<EcoString>) {
|
||||
let len = self.len();
|
||||
@ -695,6 +710,14 @@ impl<'a> LinkedNode<'a> {
|
||||
Some(next)
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether an error follows directly after the node.
|
||||
pub fn before_error(&self) -> bool {
|
||||
let Some(parent) = self.parent() else { return false };
|
||||
let Some(index) = self.index.checked_add(1) else { return false };
|
||||
let Some(node) = parent.node.children().nth(index) else { return false };
|
||||
node.kind().is_error()
|
||||
}
|
||||
}
|
||||
|
||||
/// Access to leafs.
|
||||
@ -865,8 +888,8 @@ mod tests {
|
||||
|
||||
// Go back to "#set". Skips the space.
|
||||
let prev = node.prev_sibling().unwrap();
|
||||
assert_eq!(prev.offset(), 0);
|
||||
assert_eq!(prev.text(), "#set");
|
||||
assert_eq!(prev.offset(), 1);
|
||||
assert_eq!(prev.text(), "set");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -875,7 +898,7 @@ mod tests {
|
||||
let leaf = LinkedNode::new(source.root()).leaf_at(6).unwrap();
|
||||
let prev = leaf.prev_leaf().unwrap();
|
||||
assert_eq!(leaf.text(), "fun");
|
||||
assert_eq!(prev.text(), "#set");
|
||||
assert_eq!(prev.text(), "set");
|
||||
|
||||
let source = Source::detached("#let x = 10");
|
||||
let leaf = LinkedNode::new(source.root()).leaf_at(9).unwrap();
|
||||
|
@ -18,9 +18,7 @@ pub fn parse(text: &str) -> SyntaxNode {
|
||||
/// This is only used for syntax highlighting.
|
||||
pub fn parse_code(text: &str) -> SyntaxNode {
|
||||
let mut p = Parser::new(text, 0, LexMode::Code);
|
||||
let m = p.marker();
|
||||
code(&mut p, |_| false);
|
||||
p.wrap(m, SyntaxKind::CodeBlock);
|
||||
p.finish().into_iter().next().unwrap()
|
||||
}
|
||||
|
||||
@ -31,7 +29,15 @@ fn markup(
|
||||
mut stop: impl FnMut(SyntaxKind) -> bool,
|
||||
) {
|
||||
let m = p.marker();
|
||||
while !p.eof() && !stop(p.current) {
|
||||
let mut nesting: usize = 0;
|
||||
while !p.eof() {
|
||||
match p.current() {
|
||||
SyntaxKind::LeftBracket => nesting += 1,
|
||||
SyntaxKind::RightBracket if nesting > 0 => nesting -= 1,
|
||||
_ if stop(p.current) => break,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if p.newline() {
|
||||
at_start = true;
|
||||
if min_indent > 0 && p.column(p.current_end()) < min_indent {
|
||||
@ -54,10 +60,18 @@ pub(super) fn reparse_markup(
|
||||
text: &str,
|
||||
range: Range<usize>,
|
||||
at_start: &mut bool,
|
||||
nesting: &mut usize,
|
||||
mut stop: impl FnMut(SyntaxKind) -> bool,
|
||||
) -> Option<Vec<SyntaxNode>> {
|
||||
let mut p = Parser::new(text, range.start, LexMode::Markup);
|
||||
while !p.eof() && !stop(p.current) && p.current_start() < range.end {
|
||||
while !p.eof() && p.current_start() < range.end {
|
||||
match p.current() {
|
||||
SyntaxKind::LeftBracket => *nesting += 1,
|
||||
SyntaxKind::RightBracket if *nesting > 0 => *nesting -= 1,
|
||||
_ if stop(p.current) => break,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if p.newline() {
|
||||
*at_start = true;
|
||||
p.eat();
|
||||
@ -75,46 +89,6 @@ pub(super) fn reparse_markup(
|
||||
|
||||
fn markup_expr(p: &mut Parser, at_start: &mut bool) {
|
||||
match p.current() {
|
||||
SyntaxKind::Star => strong(p),
|
||||
SyntaxKind::Underscore => emph(p),
|
||||
SyntaxKind::HeadingMarker if *at_start => heading(p),
|
||||
SyntaxKind::ListMarker if *at_start => list_item(p),
|
||||
SyntaxKind::EnumMarker if *at_start => enum_item(p),
|
||||
SyntaxKind::TermMarker if *at_start => term_item(p),
|
||||
SyntaxKind::Dollar => equation(p),
|
||||
|
||||
SyntaxKind::HeadingMarker
|
||||
| SyntaxKind::ListMarker
|
||||
| SyntaxKind::EnumMarker
|
||||
| SyntaxKind::TermMarker
|
||||
| SyntaxKind::Colon => p.convert(SyntaxKind::Text),
|
||||
|
||||
SyntaxKind::Ident
|
||||
| SyntaxKind::Let
|
||||
| SyntaxKind::Set
|
||||
| SyntaxKind::Show
|
||||
| SyntaxKind::If
|
||||
| SyntaxKind::While
|
||||
| SyntaxKind::For
|
||||
| SyntaxKind::Import
|
||||
| SyntaxKind::Include
|
||||
| SyntaxKind::Break
|
||||
| SyntaxKind::Continue
|
||||
| SyntaxKind::Return
|
||||
| SyntaxKind::LeftBrace
|
||||
| SyntaxKind::LeftBracket => embedded_code_expr(p),
|
||||
|
||||
SyntaxKind::Text
|
||||
| SyntaxKind::Linebreak
|
||||
| SyntaxKind::Escape
|
||||
| SyntaxKind::Shorthand
|
||||
| SyntaxKind::Symbol
|
||||
| SyntaxKind::SmartQuote
|
||||
| SyntaxKind::Raw
|
||||
| SyntaxKind::Link
|
||||
| SyntaxKind::Label
|
||||
| SyntaxKind::Ref => p.eat(),
|
||||
|
||||
SyntaxKind::Space
|
||||
| SyntaxKind::Parbreak
|
||||
| SyntaxKind::LineComment
|
||||
@ -122,6 +96,34 @@ fn markup_expr(p: &mut Parser, at_start: &mut bool) {
|
||||
p.eat();
|
||||
return;
|
||||
}
|
||||
|
||||
SyntaxKind::Text
|
||||
| SyntaxKind::Linebreak
|
||||
| SyntaxKind::Escape
|
||||
| SyntaxKind::Shorthand
|
||||
| SyntaxKind::SmartQuote
|
||||
| SyntaxKind::Raw
|
||||
| SyntaxKind::Link
|
||||
| SyntaxKind::Label
|
||||
| SyntaxKind::Ref => p.eat(),
|
||||
|
||||
SyntaxKind::Hashtag => embedded_code_expr(p),
|
||||
SyntaxKind::Star => strong(p),
|
||||
SyntaxKind::Underscore => emph(p),
|
||||
SyntaxKind::HeadingMarker if *at_start => heading(p),
|
||||
SyntaxKind::ListMarker if *at_start => list_item(p),
|
||||
SyntaxKind::EnumMarker if *at_start => enum_item(p),
|
||||
SyntaxKind::TermMarker if *at_start => term_item(p),
|
||||
SyntaxKind::Dollar => formula(p),
|
||||
|
||||
SyntaxKind::LeftBracket
|
||||
| SyntaxKind::RightBracket
|
||||
| SyntaxKind::HeadingMarker
|
||||
| SyntaxKind::ListMarker
|
||||
| SyntaxKind::EnumMarker
|
||||
| SyntaxKind::TermMarker
|
||||
| SyntaxKind::Colon => p.convert(SyntaxKind::Text),
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@ -130,7 +132,7 @@ fn markup_expr(p: &mut Parser, at_start: &mut bool) {
|
||||
|
||||
fn strong(p: &mut Parser) {
|
||||
let m = p.marker();
|
||||
p.expect(SyntaxKind::Star);
|
||||
p.assert(SyntaxKind::Star);
|
||||
markup(p, false, 0, |kind| {
|
||||
kind == SyntaxKind::Star
|
||||
|| kind == SyntaxKind::Parbreak
|
||||
@ -142,7 +144,7 @@ fn strong(p: &mut Parser) {
|
||||
|
||||
fn emph(p: &mut Parser) {
|
||||
let m = p.marker();
|
||||
p.expect(SyntaxKind::Underscore);
|
||||
p.assert(SyntaxKind::Underscore);
|
||||
markup(p, false, 0, |kind| {
|
||||
kind == SyntaxKind::Underscore
|
||||
|| kind == SyntaxKind::Parbreak
|
||||
@ -154,7 +156,7 @@ fn emph(p: &mut Parser) {
|
||||
|
||||
fn heading(p: &mut Parser) {
|
||||
let m = p.marker();
|
||||
p.expect(SyntaxKind::HeadingMarker);
|
||||
p.assert(SyntaxKind::HeadingMarker);
|
||||
whitespace(p);
|
||||
markup(p, false, usize::MAX, |kind| {
|
||||
kind == SyntaxKind::Label || kind == SyntaxKind::RightBracket
|
||||
@ -164,7 +166,7 @@ fn heading(p: &mut Parser) {
|
||||
|
||||
fn list_item(p: &mut Parser) {
|
||||
let m = p.marker();
|
||||
p.expect(SyntaxKind::ListMarker);
|
||||
p.assert(SyntaxKind::ListMarker);
|
||||
let min_indent = p.column(p.prev_end());
|
||||
whitespace(p);
|
||||
markup(p, false, min_indent, |kind| kind == SyntaxKind::RightBracket);
|
||||
@ -173,7 +175,7 @@ fn list_item(p: &mut Parser) {
|
||||
|
||||
fn enum_item(p: &mut Parser) {
|
||||
let m = p.marker();
|
||||
p.expect(SyntaxKind::EnumMarker);
|
||||
p.assert(SyntaxKind::EnumMarker);
|
||||
let min_indent = p.column(p.prev_end());
|
||||
whitespace(p);
|
||||
markup(p, false, min_indent, |kind| kind == SyntaxKind::RightBracket);
|
||||
@ -182,7 +184,7 @@ fn enum_item(p: &mut Parser) {
|
||||
|
||||
fn term_item(p: &mut Parser) {
|
||||
let m = p.marker();
|
||||
p.expect(SyntaxKind::TermMarker);
|
||||
p.assert(SyntaxKind::TermMarker);
|
||||
let min_indent = p.column(p.prev_end());
|
||||
whitespace(p);
|
||||
markup(p, false, usize::MAX, |kind| {
|
||||
@ -200,17 +202,18 @@ fn whitespace(p: &mut Parser) {
|
||||
}
|
||||
}
|
||||
|
||||
fn equation(p: &mut Parser) {
|
||||
fn formula(p: &mut Parser) {
|
||||
let m = p.marker();
|
||||
p.enter(LexMode::Math);
|
||||
p.expect(SyntaxKind::Dollar);
|
||||
p.assert(SyntaxKind::Dollar);
|
||||
math(p, |kind| kind == SyntaxKind::Dollar);
|
||||
p.expect(SyntaxKind::Dollar);
|
||||
p.exit();
|
||||
p.wrap(m, SyntaxKind::Math);
|
||||
p.wrap(m, SyntaxKind::Formula);
|
||||
}
|
||||
|
||||
fn math(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) {
|
||||
let m = p.marker();
|
||||
while !p.eof() && !stop(p.current()) {
|
||||
let prev = p.prev_end();
|
||||
math_expr(p);
|
||||
@ -218,6 +221,7 @@ fn math(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) {
|
||||
p.unexpected();
|
||||
}
|
||||
}
|
||||
p.wrap(m, SyntaxKind::Math);
|
||||
}
|
||||
|
||||
fn math_expr(p: &mut Parser) {
|
||||
@ -227,45 +231,44 @@ fn math_expr(p: &mut Parser) {
|
||||
fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) {
|
||||
let m = p.marker();
|
||||
match p.current() {
|
||||
SyntaxKind::Ident => {
|
||||
SyntaxKind::Hashtag => embedded_code_expr(p),
|
||||
SyntaxKind::MathIdent => {
|
||||
p.eat();
|
||||
if p.directly_at(SyntaxKind::Atom) && p.current_text() == "(" {
|
||||
if p.directly_at(SyntaxKind::MathAtom) && p.current_text() == "(" {
|
||||
math_args(p);
|
||||
p.wrap(m, SyntaxKind::FuncCall);
|
||||
} else {
|
||||
while p.directly_at(SyntaxKind::MathAtom)
|
||||
&& p.current_text() == "."
|
||||
&& matches!(
|
||||
p.lexer.clone().next(),
|
||||
SyntaxKind::MathIdent | SyntaxKind::MathAtom
|
||||
)
|
||||
{
|
||||
p.convert(SyntaxKind::Dot);
|
||||
p.convert(SyntaxKind::Ident);
|
||||
p.wrap(m, SyntaxKind::FieldAccess);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SyntaxKind::Atom if math_class(p.current_text()) == Some(MathClass::Fence) => {
|
||||
math_delimited(p, MathClass::Fence)
|
||||
SyntaxKind::MathAtom => {
|
||||
if math_class(p.current_text()) == Some(MathClass::Fence) {
|
||||
math_delimited(p, MathClass::Fence)
|
||||
} else if math_class(p.current_text()) == Some(MathClass::Opening) {
|
||||
math_delimited(p, MathClass::Closing)
|
||||
} else {
|
||||
p.eat()
|
||||
}
|
||||
}
|
||||
|
||||
SyntaxKind::Atom if math_class(p.current_text()) == Some(MathClass::Opening) => {
|
||||
math_delimited(p, MathClass::Closing)
|
||||
}
|
||||
|
||||
SyntaxKind::Let
|
||||
| SyntaxKind::Set
|
||||
| SyntaxKind::Show
|
||||
| SyntaxKind::If
|
||||
| SyntaxKind::While
|
||||
| SyntaxKind::For
|
||||
| SyntaxKind::Import
|
||||
| SyntaxKind::Include
|
||||
| SyntaxKind::Break
|
||||
| SyntaxKind::Continue
|
||||
| SyntaxKind::Return
|
||||
| SyntaxKind::LeftBrace
|
||||
| SyntaxKind::LeftBracket => embedded_code_expr(p),
|
||||
|
||||
SyntaxKind::Atom
|
||||
| SyntaxKind::Linebreak
|
||||
SyntaxKind::Linebreak
|
||||
| SyntaxKind::Escape
|
||||
| SyntaxKind::Shorthand
|
||||
| SyntaxKind::Symbol
|
||||
| SyntaxKind::AlignPoint
|
||||
| SyntaxKind::MathAlignPoint
|
||||
| SyntaxKind::Str => p.eat(),
|
||||
|
||||
_ => return,
|
||||
_ => p.expected("expression"),
|
||||
}
|
||||
|
||||
while !p.eof() && !p.at(stop) {
|
||||
@ -282,10 +285,19 @@ fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) {
|
||||
ast::Assoc::Right => {}
|
||||
}
|
||||
|
||||
if kind == SyntaxKind::MathFrac {
|
||||
math_unparen(p, m);
|
||||
}
|
||||
|
||||
p.eat();
|
||||
let m2 = p.marker();
|
||||
math_expr_prec(p, prec, stop);
|
||||
math_unparen(p, m2);
|
||||
|
||||
if p.eat_if(SyntaxKind::Underscore) || p.eat_if(SyntaxKind::Hat) {
|
||||
let m3 = p.marker();
|
||||
math_expr_prec(p, prec, SyntaxKind::Eof);
|
||||
math_unparen(p, m3);
|
||||
}
|
||||
|
||||
p.wrap(m, kind);
|
||||
@ -294,11 +306,13 @@ fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) {
|
||||
|
||||
fn math_delimited(p: &mut Parser, stop: MathClass) {
|
||||
let m = p.marker();
|
||||
p.expect(SyntaxKind::Atom);
|
||||
p.assert(SyntaxKind::MathAtom);
|
||||
let m2 = p.marker();
|
||||
while !p.eof() && !p.at(SyntaxKind::Dollar) {
|
||||
if math_class(p.current_text()) == Some(stop) {
|
||||
p.eat();
|
||||
p.wrap(m, SyntaxKind::Delimited);
|
||||
p.wrap(m2, SyntaxKind::Math);
|
||||
p.assert(SyntaxKind::MathAtom);
|
||||
p.wrap(m, SyntaxKind::MathDelimited);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -310,6 +324,22 @@ fn math_delimited(p: &mut Parser, stop: MathClass) {
|
||||
}
|
||||
}
|
||||
|
||||
fn math_unparen(p: &mut Parser, m: Marker) {
|
||||
let Some(node) = p.nodes.get_mut(m.0) else { return };
|
||||
if node.kind() != SyntaxKind::MathDelimited {
|
||||
return;
|
||||
}
|
||||
|
||||
if let [first, .., last] = node.children_mut() {
|
||||
if first.text() == "(" && last.text() == ")" {
|
||||
first.convert_to_kind(SyntaxKind::LeftParen);
|
||||
last.convert_to_kind(SyntaxKind::RightParen);
|
||||
}
|
||||
}
|
||||
|
||||
node.convert_to_kind(SyntaxKind::Math);
|
||||
}
|
||||
|
||||
fn math_class(text: &str) -> Option<MathClass> {
|
||||
let mut chars = text.chars();
|
||||
chars
|
||||
@ -321,20 +351,20 @@ fn math_class(text: &str) -> Option<MathClass> {
|
||||
fn math_op(kind: SyntaxKind) -> Option<(SyntaxKind, SyntaxKind, ast::Assoc, usize)> {
|
||||
match kind {
|
||||
SyntaxKind::Underscore => {
|
||||
Some((SyntaxKind::Script, SyntaxKind::Hat, ast::Assoc::Right, 2))
|
||||
Some((SyntaxKind::MathScript, SyntaxKind::Hat, ast::Assoc::Right, 2))
|
||||
}
|
||||
SyntaxKind::Hat => {
|
||||
Some((SyntaxKind::Script, SyntaxKind::Underscore, ast::Assoc::Right, 2))
|
||||
Some((SyntaxKind::MathScript, SyntaxKind::Underscore, ast::Assoc::Right, 2))
|
||||
}
|
||||
SyntaxKind::Slash => {
|
||||
Some((SyntaxKind::Frac, SyntaxKind::Eof, ast::Assoc::Left, 1))
|
||||
Some((SyntaxKind::MathFrac, SyntaxKind::Eof, ast::Assoc::Left, 1))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn math_args(p: &mut Parser) {
|
||||
p.expect(SyntaxKind::Atom);
|
||||
p.assert(SyntaxKind::MathAtom);
|
||||
let m = p.marker();
|
||||
let mut m2 = p.marker();
|
||||
while !p.eof() && !p.at(SyntaxKind::Dollar) {
|
||||
@ -359,10 +389,11 @@ fn math_args(p: &mut Parser) {
|
||||
p.wrap(m2, SyntaxKind::Math);
|
||||
}
|
||||
p.wrap(m, SyntaxKind::Args);
|
||||
p.expect(SyntaxKind::Atom);
|
||||
p.expect(SyntaxKind::MathAtom);
|
||||
}
|
||||
|
||||
fn code(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) {
|
||||
let m = p.marker();
|
||||
while !p.eof() && !stop(p.current()) {
|
||||
p.stop_at_newline(true);
|
||||
let prev = p.prev_end();
|
||||
@ -379,6 +410,7 @@ fn code(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) {
|
||||
p.unexpected();
|
||||
}
|
||||
}
|
||||
p.wrap(m, SyntaxKind::Code);
|
||||
}
|
||||
|
||||
fn code_expr(p: &mut Parser) {
|
||||
@ -386,6 +418,10 @@ fn code_expr(p: &mut Parser) {
|
||||
}
|
||||
|
||||
fn embedded_code_expr(p: &mut Parser) {
|
||||
p.stop_at_newline(true);
|
||||
p.enter(LexMode::Code);
|
||||
p.assert(SyntaxKind::Hashtag);
|
||||
|
||||
let stmt = matches!(
|
||||
p.current(),
|
||||
SyntaxKind::Let
|
||||
@ -395,13 +431,12 @@ fn embedded_code_expr(p: &mut Parser) {
|
||||
| SyntaxKind::Include
|
||||
);
|
||||
|
||||
p.stop_at_newline(true);
|
||||
p.enter(LexMode::Code);
|
||||
code_expr_prec(p, true, 0);
|
||||
let semi = p.eat_if(SyntaxKind::Semicolon);
|
||||
if stmt && !semi && !p.eof() && !p.at(SyntaxKind::RightBracket) {
|
||||
p.expected("semicolon or line break");
|
||||
}
|
||||
|
||||
p.exit();
|
||||
p.unstop();
|
||||
}
|
||||
@ -424,7 +459,10 @@ fn code_expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if atomic {
|
||||
let at_field_or_method =
|
||||
p.directly_at(SyntaxKind::Dot) && p.lexer.clone().next() == SyntaxKind::Ident;
|
||||
|
||||
if atomic && !at_field_or_method {
|
||||
break;
|
||||
}
|
||||
|
||||
@ -480,7 +518,7 @@ fn code_primary(p: &mut Parser, atomic: bool) {
|
||||
p.eat();
|
||||
if !atomic && p.at(SyntaxKind::Arrow) {
|
||||
p.wrap(m, SyntaxKind::Params);
|
||||
p.expect(SyntaxKind::Arrow);
|
||||
p.assert(SyntaxKind::Arrow);
|
||||
code_expr(p);
|
||||
p.wrap(m, SyntaxKind::Closure);
|
||||
}
|
||||
@ -489,7 +527,7 @@ fn code_primary(p: &mut Parser, atomic: bool) {
|
||||
SyntaxKind::LeftBrace => code_block(p),
|
||||
SyntaxKind::LeftBracket => content_block(p),
|
||||
SyntaxKind::LeftParen => with_paren(p),
|
||||
SyntaxKind::Dollar => equation(p),
|
||||
SyntaxKind::Dollar => formula(p),
|
||||
SyntaxKind::Let => let_binding(p),
|
||||
SyntaxKind::Set => set_rule(p),
|
||||
SyntaxKind::Show => show_rule(p),
|
||||
@ -536,7 +574,7 @@ fn code_block(p: &mut Parser) {
|
||||
let m = p.marker();
|
||||
p.enter(LexMode::Code);
|
||||
p.stop_at_newline(false);
|
||||
p.expect(SyntaxKind::LeftBrace);
|
||||
p.assert(SyntaxKind::LeftBrace);
|
||||
code(p, |kind| kind == SyntaxKind::RightBrace);
|
||||
p.expect(SyntaxKind::RightBrace);
|
||||
p.exit();
|
||||
@ -547,7 +585,7 @@ fn code_block(p: &mut Parser) {
|
||||
fn content_block(p: &mut Parser) {
|
||||
let m = p.marker();
|
||||
p.enter(LexMode::Markup);
|
||||
p.expect(SyntaxKind::LeftBracket);
|
||||
p.assert(SyntaxKind::LeftBracket);
|
||||
markup(p, true, 0, |kind| kind == SyntaxKind::RightBracket);
|
||||
p.expect(SyntaxKind::RightBracket);
|
||||
p.exit();
|
||||
@ -560,7 +598,7 @@ fn with_paren(p: &mut Parser) {
|
||||
if p.at(SyntaxKind::Arrow) {
|
||||
validate_params(p, m);
|
||||
p.wrap(m, SyntaxKind::Params);
|
||||
p.expect(SyntaxKind::Arrow);
|
||||
p.assert(SyntaxKind::Arrow);
|
||||
code_expr(p);
|
||||
kind = SyntaxKind::Closure;
|
||||
}
|
||||
@ -574,7 +612,7 @@ fn with_paren(p: &mut Parser) {
|
||||
|
||||
fn collection(p: &mut Parser, keyed: bool) -> SyntaxKind {
|
||||
p.stop_at_newline(false);
|
||||
p.expect(SyntaxKind::LeftParen);
|
||||
p.assert(SyntaxKind::LeftParen);
|
||||
|
||||
let mut count = 0;
|
||||
let mut parenthesized = true;
|
||||
|
@ -114,20 +114,30 @@ fn try_reparse(
|
||||
end += 1;
|
||||
}
|
||||
|
||||
// Synthesize what `at_start` would be at the start of the reparse.
|
||||
// Also take hashtag.
|
||||
if start > 0 && children[start - 1].kind() == SyntaxKind::Hashtag {
|
||||
start -= 1;
|
||||
}
|
||||
|
||||
// Synthesize what `at_start` and `nesting` would be at the start of the
|
||||
// reparse.
|
||||
let mut prefix_len = 0;
|
||||
let mut nesting = 0;
|
||||
let mut at_start = true;
|
||||
for child in &children[..start] {
|
||||
prefix_len += child.len();
|
||||
next_at_start(child, &mut at_start);
|
||||
next_nesting(child, &mut nesting);
|
||||
}
|
||||
|
||||
// Determine what `at_start` will have to be at the end of the reparse.
|
||||
let mut prev_len = 0;
|
||||
let mut prev_at_start_after = at_start;
|
||||
let mut prev_nesting_after = nesting;
|
||||
for child in &children[start..end] {
|
||||
prev_len += child.len();
|
||||
next_at_start(child, &mut prev_at_start_after);
|
||||
next_nesting(child, &mut prev_nesting_after);
|
||||
}
|
||||
|
||||
let shifted = offset + prefix_len;
|
||||
@ -139,11 +149,11 @@ fn try_reparse(
|
||||
};
|
||||
|
||||
if let Some(newborns) =
|
||||
reparse_markup(text, new_range.clone(), &mut at_start, |kind| {
|
||||
reparse_markup(text, new_range.clone(), &mut at_start, &mut nesting, |kind| {
|
||||
kind == stop_kind
|
||||
})
|
||||
{
|
||||
if at_start == prev_at_start_after {
|
||||
if at_start == prev_at_start_after && nesting == prev_nesting_after {
|
||||
return node
|
||||
.replace_children(start..end, newborns)
|
||||
.is_ok()
|
||||
@ -188,6 +198,17 @@ fn next_at_start(node: &SyntaxNode, at_start: &mut bool) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Update `nesting` based on the node.
|
||||
fn next_nesting(node: &SyntaxNode, nesting: &mut usize) {
|
||||
if node.kind() == SyntaxKind::Text {
|
||||
match node.text().as_str() {
|
||||
"[" => *nesting += 1,
|
||||
"]" if *nesting > 0 => *nesting -= 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ops::Range;
|
||||
@ -209,9 +230,13 @@ mod tests {
|
||||
panic!("test failed");
|
||||
}
|
||||
if incremental {
|
||||
assert_ne!(source.len_bytes(), range.len());
|
||||
assert_ne!(source.len_bytes(), range.len(), "should have been incremental");
|
||||
} else {
|
||||
assert_eq!(source.len_bytes(), range.len());
|
||||
assert_eq!(
|
||||
source.len_bytes(),
|
||||
range.len(),
|
||||
"shouldn't have been incremental"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,6 +245,7 @@ mod tests {
|
||||
test("abc~def~ghi", 5..6, "+", true);
|
||||
test("~~~~~~~", 3..4, "A", true);
|
||||
test("abc~~", 1..2, "", true);
|
||||
test("#var. hello", 5..6, " ", false);
|
||||
test("#var;hello", 9..10, "a", false);
|
||||
test("https:/world", 7..7, "/", false);
|
||||
test("hello world", 7..12, "walkers", false);
|
||||
@ -228,8 +254,8 @@ mod tests {
|
||||
test("a d e", 1..3, " b c d", false);
|
||||
test("~*~*~", 2..2, "*", false);
|
||||
test("::1\n2. a\n3", 7..7, "4", true);
|
||||
test("* {1+2} *", 5..6, "3", true);
|
||||
test("{(0, 1, 2)}", 5..6, "11pt", false);
|
||||
test("* #{1+2} *", 6..7, "3", true);
|
||||
test("#{(0, 1, 2)}", 6..7, "11pt", true);
|
||||
test("\n= A heading", 4..4, "n evocative", false);
|
||||
test("#call() abc~d", 7..7, "[]", true);
|
||||
test("a your thing a", 6..7, "a", false);
|
||||
@ -239,24 +265,26 @@ mod tests {
|
||||
test("#for", 4..4, "//", false);
|
||||
test("a\n#let \nb", 7..7, "i", true);
|
||||
test("#let x = (1, 2 + ;~ Five\r\n\r", 20..23, "2.", true);
|
||||
test(r"{{let x = z}; a = 1} b", 6..6, "//", false);
|
||||
test(r"#{{let x = z}; a = 1} b", 7..7, "//", false);
|
||||
test(r#"a ```typst hello```"#, 16..17, "", false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reparse_block() {
|
||||
test("Hello { x + 1 }!", 8..9, "abc", true);
|
||||
test("A{}!", 2..2, "\"", false);
|
||||
test("{ [= x] }!", 4..4, "=", true);
|
||||
test("[[]]", 2..2, "\\", false);
|
||||
test("[[ab]]", 3..4, "\\", false);
|
||||
test("{}}", 1..1, "{", false);
|
||||
test("A: [BC]", 5..5, "{", false);
|
||||
test("A: [BC]", 5..5, "{}", true);
|
||||
test("{\"ab\"}A", 4..4, "c", true);
|
||||
test("{\"ab\"}A", 4..5, "c", false);
|
||||
test("a[]b", 2..2, "{", false);
|
||||
test("a{call(); abc}b", 7..7, "[]", true);
|
||||
test("Hello #{ x + 1 }!", 9..10, "abc", true);
|
||||
test("A#{}!", 3..3, "\"", false);
|
||||
test("#{ [= x] }!", 5..5, "=", true);
|
||||
test("#[[]]", 3..3, "\\", false);
|
||||
test("#[[ab]]", 4..5, "\\", false);
|
||||
test("#{}}", 2..2, "{", false);
|
||||
test("A: #[BC]", 6..6, "{", true);
|
||||
test("A: #[BC]", 6..6, "#{", false);
|
||||
test("A: #[BC]", 6..6, "#{}", true);
|
||||
test("#{\"ab\"}A", 5..5, "c", true);
|
||||
test("#{\"ab\"}A", 5..6, "c", false);
|
||||
test("a#[]b", 3..3, "#{", false);
|
||||
test("a#{call(); abc}b", 8..8, "[]", true);
|
||||
test("a #while x {\n g(x) \n} b", 12..12, "//", true);
|
||||
test("a#[]b", 3..3, "[hey]", true);
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ No heading
|
||||
|
||||
// Parsed as headings if at start of the context.
|
||||
/**/ = Level 1
|
||||
{[== Level 2]}
|
||||
#{[== Level 2]}
|
||||
#box[=== Level 3]
|
||||
|
||||
// Not at the start of the context.
|
||||
@ -33,7 +33,7 @@ No = heading
|
||||
---
|
||||
// Blocks can continue the heading.
|
||||
|
||||
= [This
|
||||
= #[This
|
||||
is
|
||||
multiline.
|
||||
]
|
||||
|
@ -24,7 +24,7 @@ _Shopping list_
|
||||
|
||||
---
|
||||
- Level 1
|
||||
- Level [
|
||||
- Level #[
|
||||
2 through content block
|
||||
]
|
||||
|
||||
|
@ -7,19 +7,19 @@
|
||||
#set page(width: 150pt)
|
||||
|
||||
// Empty.
|
||||
{()}
|
||||
#{()}
|
||||
|
||||
// Not an array, just a parenthesized expression.
|
||||
{(1)}
|
||||
#{(1)}
|
||||
|
||||
// One item and trailing comma.
|
||||
{(-1,)}
|
||||
#{(-1,)}
|
||||
|
||||
// No trailing comma.
|
||||
{(true, false)}
|
||||
#{(true, false)}
|
||||
|
||||
// Multiple lines and items and trailing comma.
|
||||
{("1"
|
||||
#{("1"
|
||||
, rgb("002")
|
||||
,)}
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
|
||||
---
|
||||
// Test lvalue and rvalue access.
|
||||
{
|
||||
#{
|
||||
let array = (1, 2)
|
||||
array.at(1) += 5 + array.at(0)
|
||||
test(array, (1, 8))
|
||||
@ -38,7 +38,7 @@
|
||||
|
||||
---
|
||||
// Test different lvalue method.
|
||||
{
|
||||
#{
|
||||
let array = (1, 2, 3)
|
||||
array.first() = 7
|
||||
array.at(1) *= 8
|
||||
@ -47,12 +47,12 @@
|
||||
|
||||
---
|
||||
// Test rvalue out of bounds.
|
||||
// Error: 2-17 array index out of bounds (index: 5, len: 3)
|
||||
{(1, 2, 3).at(5)}
|
||||
// Error: 3-18 array index out of bounds (index: 5, len: 3)
|
||||
#{(1, 2, 3).at(5)}
|
||||
|
||||
---
|
||||
// Test lvalue out of bounds.
|
||||
{
|
||||
#{
|
||||
let array = (1, 2, 3)
|
||||
// Error: 3-14 array index out of bounds (index: 3, len: 3)
|
||||
array.at(3) = 5
|
||||
@ -60,19 +60,19 @@
|
||||
|
||||
---
|
||||
// Test bad lvalue.
|
||||
// Error: 2:3-2:14 cannot mutate a temporary value
|
||||
// Error: 2:4-2:15 cannot mutate a temporary value
|
||||
#let array = (1, 2, 3)
|
||||
{ array.len() = 4 }
|
||||
#{ array.len() = 4 }
|
||||
|
||||
---
|
||||
// Test bad lvalue.
|
||||
// Error: 2:3-2:15 type array has no method `yolo`
|
||||
// Error: 2:4-2:16 type array has no method `yolo`
|
||||
#let array = (1, 2, 3)
|
||||
{ array.yolo() = 4 }
|
||||
#{ array.yolo() = 4 }
|
||||
|
||||
---
|
||||
// Test negative indices.
|
||||
{
|
||||
#{
|
||||
let array = (1, 2, 3, 4)
|
||||
test(array.at(0), 1)
|
||||
test(array.at(-1), 4)
|
||||
@ -89,16 +89,16 @@
|
||||
#test((1, 2, 3).last(), 3)
|
||||
|
||||
---
|
||||
// Error: 3-13 array is empty
|
||||
{ ().first() }
|
||||
// Error: 4-14 array is empty
|
||||
#{ ().first() }
|
||||
|
||||
---
|
||||
// Error: 3-12 array is empty
|
||||
{ ().last() }
|
||||
// Error: 4-13 array is empty
|
||||
#{ ().last() }
|
||||
|
||||
---
|
||||
// Test the `push` and `pop` methods.
|
||||
{
|
||||
#{
|
||||
let tasks = (a: (1, 2, 3), b: (4, 5, 6))
|
||||
tasks.at("a").pop()
|
||||
tasks.b.push(7)
|
||||
@ -108,7 +108,7 @@
|
||||
|
||||
---
|
||||
// Test the `insert` and `remove` methods.
|
||||
{
|
||||
#{
|
||||
let array = (0, 1, 2, 4, 5)
|
||||
array.insert(3, 3)
|
||||
test(array, range(6))
|
||||
@ -117,9 +117,10 @@
|
||||
}
|
||||
|
||||
---
|
||||
// Error: 2:17-2:19 missing argument: index
|
||||
// Error: 2:18-2:20 missing argument: index
|
||||
#let numbers = ()
|
||||
{ numbers.insert() }
|
||||
#{ numbers.insert() }
|
||||
|
||||
---
|
||||
// Test the `slice` method.
|
||||
#test((1, 2, 3, 4).slice(2), (3, 4))
|
||||
@ -132,12 +133,12 @@
|
||||
#test("ABCD".split("").slice(1, -1).join("-"), "A-B-C-D")
|
||||
|
||||
---
|
||||
// Error: 3-31 array index out of bounds (index: 12, len: 10)
|
||||
{ range(10).slice(9, count: 3) }
|
||||
// Error: 4-32 array index out of bounds (index: 12, len: 10)
|
||||
#{ range(10).slice(9, count: 3) }
|
||||
|
||||
---
|
||||
// Error: 3-25 array index out of bounds (index: -4, len: 3)
|
||||
{ (1, 2, 3).slice(0, -4) }
|
||||
// Error: 4-26 array index out of bounds (index: -4, len: 3)
|
||||
#{ (1, 2, 3).slice(0, -4) }
|
||||
|
||||
---
|
||||
// Test the `position` method.
|
||||
@ -162,8 +163,8 @@
|
||||
#test((1, 2, 3, 4).fold(0, (s, x) => s + x), 10)
|
||||
|
||||
---
|
||||
// Error: 21-31 function must have exactly two parameters
|
||||
{ (1, 2, 3).fold(0, () => none) }
|
||||
// Error: 22-32 function must have exactly two parameters
|
||||
#{ (1, 2, 3).fold(0, () => none) }
|
||||
|
||||
---
|
||||
// Test the `rev` method.
|
||||
@ -177,17 +178,17 @@
|
||||
#test("(" + ("a", "b", "c").join(", ") + ")", "(a, b, c)")
|
||||
|
||||
---
|
||||
// Error: 2-22 cannot join boolean with boolean
|
||||
{(true, false).join()}
|
||||
// Error: 3-23 cannot join boolean with boolean
|
||||
#{(true, false).join()}
|
||||
|
||||
---
|
||||
// Error: 2-20 cannot join string with integer
|
||||
{("a", "b").join(1)}
|
||||
// Error: 3-21 cannot join string with integer
|
||||
#{("a", "b").join(1)}
|
||||
|
||||
---
|
||||
// Test joining content.
|
||||
// Ref: true
|
||||
{([One], [Two], [Three]).join([, ], last: [ and ])}.
|
||||
#{([One], [Two], [Three]).join([, ], last: [ and ])}.
|
||||
|
||||
---
|
||||
// Test the `sorted` method.
|
||||
@ -197,37 +198,37 @@
|
||||
#test((2, 1, 3, 10, 5, 8, 6, -7, 2).sorted(), (-7, 1, 2, 2, 3, 5, 6, 8, 10))
|
||||
|
||||
---
|
||||
// Error: 2-26 cannot order content and content
|
||||
{([Hi], [There]).sorted()}
|
||||
// Error: 3-27 cannot order content and content
|
||||
#{([Hi], [There]).sorted()}
|
||||
|
||||
---
|
||||
// Error: 2-18 array index out of bounds (index: -4, len: 3)
|
||||
{(1, 2, 3).at(-4)}
|
||||
// Error: 3-19 array index out of bounds (index: -4, len: 3)
|
||||
#{(1, 2, 3).at(-4)}
|
||||
|
||||
---
|
||||
// Error: 3 expected closing paren
|
||||
{(}
|
||||
// Error: 4 expected closing paren
|
||||
#{(}
|
||||
|
||||
// Error: 2-3 unexpected closing paren
|
||||
{)}
|
||||
// Error: 3-4 unexpected closing paren
|
||||
#{)}
|
||||
|
||||
// Error: 4-6 unexpected end of block comment
|
||||
{(1*/2)}
|
||||
// Error: 5-7 unexpected end of block comment
|
||||
#{(1*/2)}
|
||||
|
||||
// Error: 6-8 invalid number suffix
|
||||
{(1, 1u 2)}
|
||||
// Error: 7-9 invalid number suffix
|
||||
#{(1, 1u 2)}
|
||||
|
||||
// Error: 3-4 unexpected comma
|
||||
{(,1)}
|
||||
// Error: 4-5 unexpected comma
|
||||
#{(,1)}
|
||||
|
||||
// Missing expression makes named pair incomplete, making this an empty array.
|
||||
// Error: 5 expected expression
|
||||
{(a:)}
|
||||
// Error: 6 expected expression
|
||||
#{(a:)}
|
||||
|
||||
// Named pair after this is already identified as an array.
|
||||
// Error: 6-10 expected expression, found named pair
|
||||
{(1, b: 2)}
|
||||
// Error: 7-11 expected expression, found named pair
|
||||
#{(1, b: 2)}
|
||||
|
||||
// Keyed pair after this is already identified as an array.
|
||||
// Error: 6-14 expected expression, found keyed pair
|
||||
{(1, "key": 2)}
|
||||
// Error: 7-15 expected expression, found keyed pair
|
||||
#{(1, "key": 2)}
|
||||
|
@ -7,7 +7,7 @@
|
||||
// ... but also "content" values. While these contain markup,
|
||||
// they are also values and can be summed, stored in arrays etc.
|
||||
// There are also more standard control flow structures, like #if and #for.
|
||||
#let university = [*Technische Universität {city}*]
|
||||
#let university = [*Technische Universität #{city}*]
|
||||
#let faculty = [*Fakultät II, Institut for Mathematik*]
|
||||
|
||||
// The `box` function just places content into a rectangular container. When
|
||||
|
@ -5,14 +5,14 @@
|
||||
// Ref: true
|
||||
|
||||
// Evaluates to join of none, [My ] and the two loop bodies.
|
||||
{
|
||||
#{
|
||||
let parts = ("my fri", "end.")
|
||||
[Hello, ]
|
||||
for s in parts [{s}]
|
||||
for s in parts [#s]
|
||||
}
|
||||
|
||||
// Evaluates to join of the content and strings.
|
||||
{
|
||||
#{
|
||||
[How]
|
||||
if true {
|
||||
" are"
|
||||
@ -50,7 +50,7 @@
|
||||
|
||||
---
|
||||
// Some things can't be joined.
|
||||
{
|
||||
#{
|
||||
[A]
|
||||
// Error: 3-4 cannot join content with integer
|
||||
1
|
||||
@ -59,7 +59,7 @@
|
||||
|
||||
---
|
||||
// Block directly in markup also creates a scope.
|
||||
{ let x = 1 }
|
||||
#{ let x = 1 }
|
||||
|
||||
// Error: 7-8 unknown variable
|
||||
#test(x, 1)
|
||||
@ -73,22 +73,22 @@
|
||||
|
||||
#test(a, 1)
|
||||
|
||||
// Error: 2-3 unknown variable
|
||||
{b}
|
||||
// Error: 3-4 unknown variable
|
||||
#{b}
|
||||
|
||||
---
|
||||
// Double block creates a scope.
|
||||
{{
|
||||
#{{
|
||||
import "module.typ": b
|
||||
test(b, 1)
|
||||
}}
|
||||
|
||||
// Error: 2-3 unknown variable
|
||||
{b}
|
||||
// Error: 3-4 unknown variable
|
||||
#{b}
|
||||
|
||||
---
|
||||
// Multiple nested scopes.
|
||||
{
|
||||
#{
|
||||
let a = "a1"
|
||||
{
|
||||
let a = "a2"
|
||||
@ -104,28 +104,28 @@
|
||||
|
||||
---
|
||||
// Content blocks also create a scope.
|
||||
[#let x = 1]
|
||||
#[#let x = 1]
|
||||
|
||||
// Error: 2-3 unknown variable
|
||||
{x}
|
||||
// Error: 3-4 unknown variable
|
||||
#{x}
|
||||
|
||||
---
|
||||
// Multiple unseparated expressions in one line.
|
||||
|
||||
// Error: 2-4 invalid number suffix
|
||||
{1u}
|
||||
// Error: 3-5 invalid number suffix
|
||||
#{1u}
|
||||
|
||||
// Should output `1`.
|
||||
// Error: 3 expected semicolon or line break
|
||||
{1 2}
|
||||
// Error: 4 expected semicolon or line break
|
||||
#{1 2}
|
||||
|
||||
// Should output `2`.
|
||||
// Error: 12 expected semicolon or line break
|
||||
// Error: 22 expected semicolon or line break
|
||||
{let x = -1 let y = 3 x + y}
|
||||
// Error: 13 expected semicolon or line break
|
||||
// Error: 23 expected semicolon or line break
|
||||
#{let x = -1 let y = 3 x + y}
|
||||
|
||||
// Should output `3`.
|
||||
{
|
||||
#{
|
||||
// Error: 6 expected identifier
|
||||
// Error: 10 expected block
|
||||
for "v"
|
||||
@ -138,9 +138,9 @@
|
||||
}
|
||||
|
||||
---
|
||||
// Error: 2 expected closing brace
|
||||
{
|
||||
// Error: 3 expected closing brace
|
||||
#{
|
||||
|
||||
---
|
||||
// Error: 1-2 unexpected closing brace
|
||||
}
|
||||
// Error: 2 expected expression
|
||||
#}
|
||||
|
@ -96,7 +96,7 @@
|
||||
#let x = { continue }
|
||||
|
||||
---
|
||||
// Error: 1-10 cannot continue outside of loop
|
||||
// Error: 2-10 cannot continue outside of loop
|
||||
#continue
|
||||
|
||||
---
|
||||
@ -104,7 +104,7 @@
|
||||
// Should output `Hello World 🌎`.
|
||||
#for _ in range(10) {
|
||||
[Hello ]
|
||||
[World {
|
||||
[World #{
|
||||
[🌎]
|
||||
break
|
||||
}]
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
// Ommitted space.
|
||||
#let f() = {}
|
||||
[#f()*Bold*]
|
||||
#[#f()*Bold*]
|
||||
|
||||
// Call return value of function with body.
|
||||
#let f(x, body) = (y) => [#x] + body + [#y]
|
||||
@ -34,7 +34,7 @@
|
||||
#test(alias(alias), "function")
|
||||
|
||||
// Callee expressions.
|
||||
{
|
||||
#{
|
||||
// Wrapped in parens.
|
||||
test((type)("hi"), "string")
|
||||
|
||||
@ -48,25 +48,25 @@
|
||||
#set text(family: "Arial", family: "Helvetica")
|
||||
|
||||
---
|
||||
// Error: 2-6 expected function, found boolean
|
||||
{true()}
|
||||
// Error: 3-7 expected function, found boolean
|
||||
#{true()}
|
||||
|
||||
---
|
||||
#let x = "x"
|
||||
|
||||
// Error: 1-3 expected function, found string
|
||||
// Error: 2-3 expected function, found string
|
||||
#x()
|
||||
|
||||
---
|
||||
#let f(x) = x
|
||||
|
||||
// Error: 1-6 expected function, found integer
|
||||
// Error: 2-6 expected function, found integer
|
||||
#f(1)(2)
|
||||
|
||||
---
|
||||
#let f(x) = x
|
||||
|
||||
// Error: 1-6 expected function, found content
|
||||
// Error: 2-6 expected function, found content
|
||||
#f[1](2)
|
||||
|
||||
---
|
||||
@ -90,16 +90,16 @@
|
||||
// Error: 7-12 expected identifier, found string
|
||||
#func("abc": 2)
|
||||
|
||||
// Error: 7-10 expected identifier, found group
|
||||
{func((x):1)}
|
||||
// Error: 8-11 expected identifier, found group
|
||||
#{func((x):1)}
|
||||
|
||||
---
|
||||
// Error: 2:1 expected closing bracket
|
||||
#func[`a]`
|
||||
|
||||
---
|
||||
// Error: 7 expected closing paren
|
||||
{func(}
|
||||
// Error: 8 expected closing paren
|
||||
#{func(}
|
||||
|
||||
---
|
||||
// Error: 2:1 expected quote
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
---
|
||||
// Basic closure without captures.
|
||||
{
|
||||
#{
|
||||
let adder = (x, y) => x + y
|
||||
test(adder(2, 3), 5)
|
||||
}
|
||||
@ -20,7 +20,7 @@
|
||||
---
|
||||
// Pass closure as argument and return closure.
|
||||
// Also uses shorthand syntax for a single argument.
|
||||
{
|
||||
#{
|
||||
let chain = (f, g) => (x) => f(g(x))
|
||||
let f = x => x + 1
|
||||
let g = x => 2 * x
|
||||
@ -30,7 +30,7 @@
|
||||
|
||||
---
|
||||
// Capture environment.
|
||||
{
|
||||
#{
|
||||
let mark = "!"
|
||||
let greet = {
|
||||
let hi = "Hi"
|
||||
@ -48,7 +48,7 @@
|
||||
|
||||
---
|
||||
// Redefined variable.
|
||||
{
|
||||
#{
|
||||
let x = 1
|
||||
let f() = {
|
||||
let x = x + 2
|
||||
@ -59,7 +59,7 @@
|
||||
|
||||
---
|
||||
// Import bindings.
|
||||
{
|
||||
#{
|
||||
let b = "module.typ"
|
||||
let f() = {
|
||||
import b: b
|
||||
@ -70,7 +70,7 @@
|
||||
|
||||
---
|
||||
// For loop bindings.
|
||||
{
|
||||
#{
|
||||
let v = (1, 2, 3)
|
||||
let f() = {
|
||||
let s = 0
|
||||
@ -82,7 +82,7 @@
|
||||
|
||||
---
|
||||
// Let + closure bindings.
|
||||
{
|
||||
#{
|
||||
let g = "hi"
|
||||
let f() = {
|
||||
let g() = "bye"
|
||||
@ -93,7 +93,7 @@
|
||||
|
||||
---
|
||||
// Parameter bindings.
|
||||
{
|
||||
#{
|
||||
let x = 5
|
||||
let g() = {
|
||||
let f(x, y: x) = x + y
|
||||
@ -105,7 +105,7 @@
|
||||
|
||||
---
|
||||
// Don't leak environment.
|
||||
{
|
||||
#{
|
||||
// Error: 16-17 unknown variable
|
||||
let func() = x
|
||||
let x = "hi"
|
||||
@ -114,7 +114,7 @@
|
||||
|
||||
---
|
||||
// Too few arguments.
|
||||
{
|
||||
#{
|
||||
let types(x, y) = "[" + type(x) + ", " + type(y) + "]"
|
||||
test(types(14%, 12pt), "[ratio, length]")
|
||||
|
||||
@ -124,7 +124,7 @@
|
||||
|
||||
---
|
||||
// Too many arguments.
|
||||
{
|
||||
#{
|
||||
let f(x) = x + 1
|
||||
|
||||
// Error: 8-13 unexpected argument
|
||||
@ -133,7 +133,7 @@
|
||||
|
||||
---
|
||||
// Named arguments.
|
||||
{
|
||||
#{
|
||||
let greet(name, birthday: false) = {
|
||||
if birthday { "Happy Birthday, " } else { "Hey, " } + name + "!"
|
||||
}
|
||||
@ -156,8 +156,8 @@
|
||||
#let f(a, b, a: none, b: none, c, b) = none
|
||||
|
||||
---
|
||||
// Error: 6-16 expected identifier, named pair or argument sink, found keyed pair
|
||||
{(a, "named": b) => none}
|
||||
// Error: 7-17 expected identifier, named pair or argument sink, found keyed pair
|
||||
#{(a, "named": b) => none}
|
||||
|
||||
---
|
||||
// Error: 10-15 expected identifier, found string
|
||||
|
@ -18,7 +18,7 @@
|
||||
---
|
||||
// The inner rectangle should also be yellow here.
|
||||
// (and therefore invisible)
|
||||
[#set rect(fill: yellow);#text(1em, rect(inset: 5pt, rect()))]
|
||||
#[#set rect(fill: yellow);#text(1em, rect(inset: 5pt, rect()))]
|
||||
|
||||
---
|
||||
// The inner rectangle should not be yellow here.
|
||||
|
@ -5,7 +5,7 @@
|
||||
// Ref: true
|
||||
|
||||
// Empty
|
||||
{(:)}
|
||||
#{(:)}
|
||||
|
||||
// Two pairs and string key.
|
||||
#let dict = (normal: 1, "spacy key": 2)
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
---
|
||||
// Test lvalue and rvalue access.
|
||||
{
|
||||
#{
|
||||
let dict = (a: 1, "b b": 1)
|
||||
dict.at("b b") += 1
|
||||
dict.state = (ok: true, err: false)
|
||||
@ -29,7 +29,7 @@
|
||||
|
||||
---
|
||||
// Test rvalue missing key.
|
||||
{
|
||||
#{
|
||||
let dict = (a: 1, b: 2)
|
||||
// Error: 11-23 dictionary does not contain key "c"
|
||||
let x = dict.at("c")
|
||||
@ -37,7 +37,7 @@
|
||||
|
||||
---
|
||||
// Missing lvalue is not automatically none-initialized.
|
||||
{
|
||||
#{
|
||||
let dict = (:)
|
||||
// Error: 3-9 dictionary does not contain key "b"
|
||||
dict.b += 1
|
||||
@ -51,39 +51,39 @@
|
||||
#test(dict.values(), (3, 1, 2))
|
||||
#test(dict.pairs((k, v) => k + str(v)).join(), "a3b1c2")
|
||||
|
||||
{ dict.remove("c") }
|
||||
#{ dict.remove("c") }
|
||||
#test("c" in dict, false)
|
||||
#test(dict, (a: 3, b: 1))
|
||||
|
||||
---
|
||||
// Error: 24-29 duplicate key
|
||||
{(first: 1, second: 2, first: 3)}
|
||||
// Error: 25-30 duplicate key
|
||||
#{(first: 1, second: 2, first: 3)}
|
||||
|
||||
---
|
||||
// Error: 17-20 duplicate key
|
||||
{(a: 1, "b": 2, "a": 3)}
|
||||
// Error: 18-21 duplicate key
|
||||
#{(a: 1, "b": 2, "a": 3)}
|
||||
|
||||
---
|
||||
// Simple expression after already being identified as a dictionary.
|
||||
// Error: 9-10 expected named or keyed pair, found identifier
|
||||
{(a: 1, b)}
|
||||
// Error: 10-11 expected named or keyed pair, found identifier
|
||||
#{(a: 1, b)}
|
||||
|
||||
// Identified as dictionary due to initial colon.
|
||||
// Error: 4-5 expected named or keyed pair, found integer
|
||||
// Error: 5 expected comma
|
||||
// Error: 12-16 expected identifier or string, found boolean
|
||||
// Error: 17 expected expression
|
||||
{(:1 b:"", true:)}
|
||||
// Error: 5-6 expected named or keyed pair, found integer
|
||||
// Error: 6 expected comma
|
||||
// Error: 13-17 expected identifier or string, found boolean
|
||||
// Error: 18 expected expression
|
||||
#{(:1 b:"", true:)}
|
||||
|
||||
// Error: 3-8 expected identifier or string, found binary expression
|
||||
{(a + b: "hey")}
|
||||
// Error: 4-9 expected identifier or string, found binary expression
|
||||
#{(a + b: "hey")}
|
||||
|
||||
---
|
||||
// Error: 3-15 cannot mutate a temporary value
|
||||
{ (key: "val").other = "some" }
|
||||
// Error: 4-16 cannot mutate a temporary value
|
||||
#{ (key: "val").other = "some" }
|
||||
|
||||
---
|
||||
{
|
||||
#{
|
||||
let object = none
|
||||
// Error: 3-9 expected dictionary, found none
|
||||
object.property = "value"
|
||||
|
@ -5,7 +5,7 @@
|
||||
// Test field on dictionary.
|
||||
#let dict = (nothing: "ness", hello: "world")
|
||||
#test(dict.nothing, "ness")
|
||||
{
|
||||
#{
|
||||
let world = dict
|
||||
.hello
|
||||
|
||||
@ -23,12 +23,12 @@
|
||||
- C
|
||||
|
||||
---
|
||||
// Error: 6-13 dictionary does not contain key "invalid"
|
||||
{(:).invalid}
|
||||
// Error: 7-14 dictionary does not contain key "invalid"
|
||||
#{(:).invalid}
|
||||
|
||||
---
|
||||
// Error: 2-7 expected dictionary or content, found boolean
|
||||
{false.ok}
|
||||
// Error: 9-11 cannot access fields on type boolean
|
||||
#{false.ok}
|
||||
|
||||
---
|
||||
// Error: 29-32 unknown field `fun`
|
||||
@ -36,6 +36,6 @@
|
||||
= A
|
||||
|
||||
---
|
||||
// Error: 8 expected identifier
|
||||
// Error: 8 expected semicolon or line break
|
||||
{false.true}
|
||||
// Error: 9 expected identifier
|
||||
// Error: 9 expected semicolon or line break
|
||||
#{false.true}
|
||||
|
@ -10,12 +10,12 @@
|
||||
// Dictionary is not traversed in insertion order.
|
||||
// Should output `Age: 2. Name: Typst.`.
|
||||
#for k, v in (Name: "Typst", Age: 2) [
|
||||
{k}: {v}.
|
||||
#k: #v.
|
||||
]
|
||||
|
||||
// Block body.
|
||||
// Should output `[1st, 2nd, 3rd, 4th]`.
|
||||
{
|
||||
#{
|
||||
"["
|
||||
for v in (1, 2, 3, 4) {
|
||||
if v > 1 [, ]
|
||||
@ -97,8 +97,8 @@
|
||||
// Error: 5 expected identifier
|
||||
#for//
|
||||
|
||||
// Error: 5 expected identifier
|
||||
{for}
|
||||
// Error: 6 expected identifier
|
||||
#{for}
|
||||
|
||||
// Error: 7 expected keyword `in`
|
||||
#for v
|
||||
|
@ -40,7 +40,7 @@
|
||||
}
|
||||
|
||||
// Content block can be argument or body depending on whitespace.
|
||||
{
|
||||
#{
|
||||
if "content" == type[b] [Fi] else [Nope]
|
||||
if "content" == type [Nope] else [ve.]
|
||||
}
|
||||
@ -76,7 +76,7 @@
|
||||
// Value of if expressions.
|
||||
// Ref: false
|
||||
|
||||
{
|
||||
#{
|
||||
let x = 1
|
||||
let y = 2
|
||||
let z
|
||||
@ -109,13 +109,13 @@
|
||||
// Error: 4 expected expression
|
||||
#if
|
||||
|
||||
// Error: 4 expected expression
|
||||
{if}
|
||||
// Error: 5 expected expression
|
||||
#{if}
|
||||
|
||||
// Error: 6 expected block
|
||||
#if x
|
||||
|
||||
// Error: 1-6 unexpected keyword `else`
|
||||
// Error: 2 expected expression
|
||||
#else {}
|
||||
|
||||
// Should output `x`.
|
||||
|
@ -49,8 +49,8 @@
|
||||
#test((module,).at(0).item(1, 2), 3)
|
||||
|
||||
// Doesn't work because of mutating name.
|
||||
// Error: 2-11 cannot mutate a temporary value
|
||||
{(module,).at(0).push()}
|
||||
// Error: 3-12 cannot mutate a temporary value
|
||||
#{(module,).at(0).push()}
|
||||
|
||||
---
|
||||
// Who needs whitespace anyways?
|
||||
|
@ -15,7 +15,7 @@
|
||||
#chap2
|
||||
|
||||
---
|
||||
{
|
||||
#{
|
||||
// Error: 19-38 file not found (searched at typ/compiler/modules/chap3.typ)
|
||||
let x = include "modules/chap3.typ"
|
||||
}
|
||||
@ -24,7 +24,7 @@
|
||||
#include "modules/chap1.typ"
|
||||
|
||||
// The variables of the file should not appear in this scope.
|
||||
// Error: 1-6 unknown variable
|
||||
// Error: 2-6 unknown variable
|
||||
#name
|
||||
|
||||
---
|
||||
|
@ -26,7 +26,7 @@ The end.
|
||||
it
|
||||
}
|
||||
|
||||
This is a thing [that <last>] happened.
|
||||
This is a thing #[that <last>] happened.
|
||||
|
||||
---
|
||||
// Test abusing dynamic labels for styling.
|
||||
@ -50,7 +50,7 @@ _Visible_
|
||||
---
|
||||
// Test that label only works within one content block.
|
||||
#show <strike>: strike
|
||||
*This is* [<strike>] *protected.*
|
||||
*This is* #[<strike>] *protected.*
|
||||
*This is not.* <strike>
|
||||
|
||||
---
|
||||
|
@ -36,8 +36,8 @@ Three
|
||||
// Error: 5 expected identifier
|
||||
#let
|
||||
|
||||
// Error: 5 expected identifier
|
||||
{let}
|
||||
// Error: 6 expected identifier
|
||||
#{let}
|
||||
|
||||
// Error: 5 expected identifier
|
||||
// Error: 5 expected semicolon or line break
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
---
|
||||
// Test mutating indexed value.
|
||||
{
|
||||
#{
|
||||
let matrix = (((1,), (2,)), ((3,), (4,)))
|
||||
matrix.at(1).at(0).push(5)
|
||||
test(matrix, (((1,), (2,)), ((3, 5), (4,))))
|
||||
@ -15,7 +15,7 @@
|
||||
|
||||
---
|
||||
// Test multiline chain in code block.
|
||||
{
|
||||
#{
|
||||
let rewritten = "Hello. This is a sentence. And one more."
|
||||
.split(".")
|
||||
.map(s => s.trim())
|
||||
@ -27,20 +27,20 @@
|
||||
}
|
||||
|
||||
---
|
||||
// Error: 2:3-2:16 type array has no method `fun`
|
||||
// Error: 2:4-2:17 type array has no method `fun`
|
||||
#let numbers = ()
|
||||
{ numbers.fun() }
|
||||
#{ numbers.fun() }
|
||||
|
||||
---
|
||||
// Error: 2:3-2:44 cannot mutate a temporary value
|
||||
// Error: 2:4-2:45 cannot mutate a temporary value
|
||||
#let numbers = (1, 2, 3)
|
||||
{ numbers.map(v => v / 2).sorted().map(str).remove(4) }
|
||||
#{ numbers.map(v => v / 2).sorted().map(str).remove(4) }
|
||||
|
||||
---
|
||||
// Error: 2:3-2:19 cannot mutate a temporary value
|
||||
// Error: 2:4-2:20 cannot mutate a temporary value
|
||||
#let numbers = (1, 2, 3)
|
||||
{ numbers.sorted() = 1 }
|
||||
#{ numbers.sorted() = 1 }
|
||||
|
||||
---
|
||||
// Error: 3-6 cannot mutate a constant
|
||||
{ box.push(1) }
|
||||
// Error: 4-7 cannot mutate a constant
|
||||
#{ box.push(1) }
|
||||
|
@ -2,8 +2,8 @@
|
||||
// Ref: false
|
||||
|
||||
---
|
||||
// Error: 3 expected expression
|
||||
{-}
|
||||
// Error: 4 expected expression
|
||||
#{-}
|
||||
|
||||
---
|
||||
// Error: 10 expected expression
|
||||
@ -14,115 +14,115 @@
|
||||
#test({2*}, 2)
|
||||
|
||||
---
|
||||
// Error: 2-12 cannot apply '+' to content
|
||||
{+([] + [])}
|
||||
// Error: 3-13 cannot apply '+' to content
|
||||
#{+([] + [])}
|
||||
|
||||
---
|
||||
// Error: 2-5 cannot apply '-' to string
|
||||
{-""}
|
||||
// Error: 3-6 cannot apply '-' to string
|
||||
#{-""}
|
||||
|
||||
---
|
||||
// Error: 2-8 cannot apply 'not' to array
|
||||
{not ()}
|
||||
// Error: 3-9 cannot apply 'not' to array
|
||||
#{not ()}
|
||||
|
||||
---
|
||||
// Error: 2-18 cannot apply '<=' to relative length and ratio
|
||||
{30% + 1pt <= 40%}
|
||||
// Error: 3-19 cannot apply '<=' to relative length and ratio
|
||||
#{30% + 1pt <= 40%}
|
||||
|
||||
---
|
||||
// Error: 2-13 cannot apply '<=' to length and length
|
||||
{1em <= 10pt}
|
||||
// Error: 3-14 cannot apply '<=' to length and length
|
||||
#{1em <= 10pt}
|
||||
|
||||
---
|
||||
// Error: 2-11 cannot divide by zero
|
||||
{1.2 / 0.0}
|
||||
// Error: 3-12 cannot divide by zero
|
||||
#{1.2 / 0.0}
|
||||
|
||||
---
|
||||
// Error: 2-7 cannot divide by zero
|
||||
{1 / 0}
|
||||
// Error: 3-8 cannot divide by zero
|
||||
#{1 / 0}
|
||||
|
||||
---
|
||||
// Error: 2-14 cannot divide by zero
|
||||
{15deg / 0deg}
|
||||
// Error: 3-15 cannot divide by zero
|
||||
#{15deg / 0deg}
|
||||
|
||||
---
|
||||
// Special messages for +, -, * and /.
|
||||
// Error: 03-10 cannot add integer and string
|
||||
{(1 + "2", 40% - 1)}
|
||||
// Error: 4-11 cannot add integer and string
|
||||
#{(1 + "2", 40% - 1)}
|
||||
|
||||
---
|
||||
// Error: 14-22 cannot add integer and string
|
||||
{ let x = 1; x += "2" }
|
||||
// Error: 15-23 cannot add integer and string
|
||||
#{ let x = 1; x += "2" }
|
||||
|
||||
---
|
||||
// Error: 3-12 cannot divide ratio by length
|
||||
{ 10% / 5pt }
|
||||
// Error: 4-13 cannot divide ratio by length
|
||||
#{ 10% / 5pt }
|
||||
|
||||
---
|
||||
// Error: 3-12 cannot divide these two lengths
|
||||
{ 1em / 5pt }
|
||||
// Error: 4-13 cannot divide these two lengths
|
||||
#{ 1em / 5pt }
|
||||
|
||||
---
|
||||
// Error: 3-19 cannot divide relative length by ratio
|
||||
{ (10% + 1pt) / 5% }
|
||||
// Error: 4-20 cannot divide relative length by ratio
|
||||
#{ (10% + 1pt) / 5% }
|
||||
|
||||
---
|
||||
// Error: 3-28 cannot divide these two relative lengths
|
||||
{ (10% + 1pt) / (20% + 1pt) }
|
||||
// Error: 4-29 cannot divide these two relative lengths
|
||||
#{ (10% + 1pt) / (20% + 1pt) }
|
||||
|
||||
---
|
||||
// Error: 12-19 cannot subtract integer from ratio
|
||||
{(1234567, 40% - 1)}
|
||||
// Error: 13-20 cannot subtract integer from ratio
|
||||
#{(1234567, 40% - 1)}
|
||||
|
||||
---
|
||||
// Error: 2-10 cannot multiply integer with boolean
|
||||
{2 * true}
|
||||
// Error: 3-11 cannot multiply integer with boolean
|
||||
#{2 * true}
|
||||
|
||||
---
|
||||
// Error: 2-10 cannot divide integer by length
|
||||
{3 / 12pt}
|
||||
// Error: 3-11 cannot divide integer by length
|
||||
#{3 / 12pt}
|
||||
|
||||
---
|
||||
// Error: 2-9 cannot repeat this string -1 times
|
||||
{-1 * ""}
|
||||
// Error: 3-10 cannot repeat this string -1 times
|
||||
#{-1 * ""}
|
||||
|
||||
---
|
||||
{
|
||||
#{
|
||||
let x = 2
|
||||
for _ in range(61) {
|
||||
(x) *= 2
|
||||
}
|
||||
// Error: 4-18 cannot repeat this string 4611686018427387904 times
|
||||
{x * "abcdefgh"}
|
||||
// Error: 3-17 cannot repeat this string 4611686018427387904 times
|
||||
x * "abcdefgh"
|
||||
}
|
||||
|
||||
---
|
||||
// Error: 4-5 unknown variable
|
||||
{ (x) = "" }
|
||||
// Error: 5-6 unknown variable
|
||||
#{ (x) = "" }
|
||||
|
||||
---
|
||||
// Error: 3-8 cannot mutate a temporary value
|
||||
{ 1 + 2 += 3 }
|
||||
// Error: 4-9 cannot mutate a temporary value
|
||||
#{ 1 + 2 += 3 }
|
||||
|
||||
---
|
||||
// Error: 2:2-2:7 cannot apply 'not' to string
|
||||
// Error: 2:3-2:8 cannot apply 'not' to string
|
||||
#let x = "Hey"
|
||||
{not x = "a"}
|
||||
#{not x = "a"}
|
||||
|
||||
---
|
||||
// Error: 7-8 unknown variable
|
||||
{ 1 + x += 3 }
|
||||
// Error: 8-9 unknown variable
|
||||
#{ 1 + x += 3 }
|
||||
|
||||
---
|
||||
// Error: 3-4 unknown variable
|
||||
{ z = 1 }
|
||||
// Error: 4-5 unknown variable
|
||||
#{ z = 1 }
|
||||
|
||||
---
|
||||
// Error: 3-7 cannot mutate a constant
|
||||
{ rect = "hi" }
|
||||
// Error: 4-8 cannot mutate a constant
|
||||
#{ rect = "hi" }
|
||||
|
||||
---
|
||||
// Works if we define rect beforehand
|
||||
// (since then it doesn't resolve to the standard library version anymore).
|
||||
#let rect = ""
|
||||
{ rect = "hi" }
|
||||
#{ rect = "hi" }
|
||||
|
@ -14,9 +14,14 @@
|
||||
|
||||
---
|
||||
// Assignment binds stronger than boolean operations.
|
||||
// Error: 2:2-2:7 cannot mutate a temporary value
|
||||
// Error: 2:3-2:8 cannot mutate a temporary value
|
||||
#let x = false
|
||||
{not x = "a"}
|
||||
#{not x = "a"}
|
||||
|
||||
---
|
||||
// Precedence doesn't matter for chained unary operators.
|
||||
// Error: 3-12 cannot apply '-' to boolean
|
||||
#{-not true}
|
||||
|
||||
---
|
||||
// Parentheses override precedence.
|
||||
@ -25,8 +30,3 @@
|
||||
|
||||
// Error: 14 expected closing paren
|
||||
#test({(1 + 1}, 2)
|
||||
|
||||
---
|
||||
// Precedence doesn't matter for chained unary operators.
|
||||
// Error: 2-11 cannot apply '-' to boolean
|
||||
{-not true}
|
||||
|
@ -4,7 +4,7 @@
|
||||
---
|
||||
// Test adding content.
|
||||
// Ref: true
|
||||
{[*Hello* ] + [world!]}
|
||||
#{[*Hello* ] + [world!]}
|
||||
|
||||
---
|
||||
// Test math operators.
|
||||
@ -176,17 +176,17 @@
|
||||
// Test assignment operators.
|
||||
|
||||
#let x = 0
|
||||
{ x = 10 } #test(x, 10)
|
||||
{ x -= 5 } #test(x, 5)
|
||||
{ x += 1 } #test(x, 6)
|
||||
{ x *= x } #test(x, 36)
|
||||
{ x /= 2.0 } #test(x, 18.0)
|
||||
{ x = "some" } #test(x, "some")
|
||||
{ x += "thing" } #test(x, "something")
|
||||
#{ x = 10 } #test(x, 10)
|
||||
#{ x -= 5 } #test(x, 5)
|
||||
#{ x += 1 } #test(x, 6)
|
||||
#{ x *= x } #test(x, 36)
|
||||
#{ x /= 2.0 } #test(x, 18.0)
|
||||
#{ x = "some" } #test(x, "some")
|
||||
#{ x += "thing" } #test(x, "something")
|
||||
|
||||
---
|
||||
// Error: 3-6 cannot mutate a constant
|
||||
{ box = 1 }
|
||||
// Error: 4-7 cannot mutate a constant
|
||||
#{ box = 1 }
|
||||
|
||||
---
|
||||
// Test `in` operator.
|
||||
@ -204,8 +204,8 @@
|
||||
/* fun comment? */ in "abc", false)
|
||||
|
||||
---
|
||||
// Error: 9 expected keyword `in`
|
||||
{"a" not}
|
||||
// Error: 10 expected keyword `in`
|
||||
#{"a" not}
|
||||
|
||||
---
|
||||
// Test `with` method.
|
||||
|
@ -2,33 +2,33 @@
|
||||
|
||||
---
|
||||
// Literal values.
|
||||
{auto} \
|
||||
{none} (empty) \
|
||||
{true} \
|
||||
{false}
|
||||
#auto \
|
||||
#none (empty) \
|
||||
#true \
|
||||
#false
|
||||
|
||||
---
|
||||
// Numerical values.
|
||||
{1} \
|
||||
{1.0e-4} \
|
||||
{3.15} \
|
||||
{1e-10} \
|
||||
{50.368%} \
|
||||
{0.0000012345pt} \
|
||||
{4.5cm} \
|
||||
{12e1pt} \
|
||||
{2.5rad} \
|
||||
{45deg} \
|
||||
{1.7em} \
|
||||
{1cm + 0em} \
|
||||
{2em + 10pt} \
|
||||
{2.3fr}
|
||||
#1 \
|
||||
#1.0e-4 \
|
||||
#3.15 \
|
||||
#1e-10 \
|
||||
#50.368% \
|
||||
#0.0000012345pt \
|
||||
#4.5cm \
|
||||
#12e1pt \
|
||||
#2.5rad \
|
||||
#45deg \
|
||||
#1.7em \
|
||||
#{1cm + 0em} \
|
||||
#{2em + 10pt} \
|
||||
#2.3fr
|
||||
|
||||
---
|
||||
// Colors and strokes.
|
||||
#set text(0.8em)
|
||||
#rgb("f7a205") \
|
||||
{2pt + rgb("f7a205")}
|
||||
#{2pt + rgb("f7a205")}
|
||||
|
||||
---
|
||||
// Strings and escaping.
|
||||
@ -43,6 +43,6 @@
|
||||
// Functions are invisible.
|
||||
Nothing
|
||||
#let f(x) = x
|
||||
{f}
|
||||
{rect}
|
||||
{() => none}
|
||||
#f
|
||||
#rect
|
||||
#{() => none}
|
||||
|
@ -3,18 +3,18 @@
|
||||
---
|
||||
// Test that text is affected by instantiation-site bold.
|
||||
#let x = [World]
|
||||
Hello *{x}*
|
||||
Hello *#x*
|
||||
|
||||
---
|
||||
// Test that lists are affected by correct indents.
|
||||
#let fruit = [
|
||||
- Apple
|
||||
- Orange
|
||||
#list(body-indent: 20pt, [Pear])
|
||||
#list(body-indent: 20pt)[Pear]
|
||||
]
|
||||
|
||||
- Fruit
|
||||
[#set list(indent: 10pt)
|
||||
#[#set list(indent: 10pt)
|
||||
#fruit]
|
||||
- No more fruit
|
||||
|
||||
@ -28,7 +28,7 @@ Hello *{x}*
|
||||
|
||||
---
|
||||
// Test that scoping works as expected.
|
||||
{
|
||||
#{
|
||||
if true {
|
||||
set text(blue)
|
||||
[Blue ]
|
||||
@ -62,5 +62,5 @@ Hello *{x}*
|
||||
#set text(red) if 1 + 2
|
||||
|
||||
---
|
||||
// Error: 11-25 set is only allowed directly in code and content blocks
|
||||
{ let x = set text(blue) }
|
||||
// Error: 12-26 set is only allowed directly in code and content blocks
|
||||
#{ let x = set text(blue) }
|
||||
|
@ -17,4 +17,4 @@ a~b
|
||||
|
||||
---
|
||||
#set text("Roboto")
|
||||
A... vs {"A..."}
|
||||
A... vs #"A..."
|
||||
|
@ -16,7 +16,7 @@ in booklovers and the great fulfiller of human need.
|
||||
|
||||
---
|
||||
// Test bare show in content block.
|
||||
A [_B #show c => [*#c*]; C_] D
|
||||
A #[_B #show c => [*#c*]; C_] D
|
||||
|
||||
---
|
||||
// Test style precedence.
|
||||
@ -29,5 +29,5 @@ Forest
|
||||
Ignored
|
||||
|
||||
---
|
||||
// Error: 4-18 show is only allowed directly in code and content blocks
|
||||
{ (show body => 2) * body }
|
||||
// Error: 5-19 show is only allowed directly in code and content blocks
|
||||
#{ (show body => 2) * body }
|
||||
|
@ -14,7 +14,7 @@
|
||||
// Test full reset.
|
||||
#show heading: [B]
|
||||
#show heading: set text(size: 10pt, weight: 400)
|
||||
A [= Heading] C
|
||||
A #[= Heading] C
|
||||
|
||||
---
|
||||
// Test full removal.
|
||||
@ -58,7 +58,7 @@ Another text.
|
||||
|
||||
---
|
||||
// Test that scoping works as expected.
|
||||
{
|
||||
#{
|
||||
let world = [ World ]
|
||||
show "W": strong
|
||||
world
|
||||
@ -100,5 +100,5 @@ Another text.
|
||||
#show red: []
|
||||
|
||||
---
|
||||
// Error: 7-25 show is only allowed directly in code and content blocks
|
||||
{ 1 + show heading: none }
|
||||
// Error: 8-26 show is only allowed directly in code and content blocks
|
||||
#{ 1 + show heading: none }
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
---
|
||||
// Test standard argument overriding.
|
||||
{
|
||||
#{
|
||||
let f(style: "normal", weight: "regular") = {
|
||||
"(style: " + style + ", weight: " + weight + ")"
|
||||
}
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
---
|
||||
// Test multiple calls.
|
||||
{
|
||||
#{
|
||||
let f(b, c: "!") = b + c
|
||||
let g(a, ..sink) = a + f(..sink)
|
||||
test(g("a", "b", c: "c"), "abc")
|
||||
@ -24,7 +24,7 @@
|
||||
|
||||
---
|
||||
// Test doing things with arguments.
|
||||
{
|
||||
#{
|
||||
let save(..args) = {
|
||||
test(type(args), "arguments")
|
||||
test(repr(args), "(1, 2, three: true)")
|
||||
@ -35,14 +35,14 @@
|
||||
|
||||
---
|
||||
// Test spreading array and dictionary.
|
||||
{
|
||||
#{
|
||||
let more = (3, -3, 6, 10)
|
||||
test(min(1, 2, ..more), -3)
|
||||
test(max(..more, 9), 10)
|
||||
test(max(..more, 11), 11)
|
||||
}
|
||||
|
||||
{
|
||||
#{
|
||||
let more = (c: 3, d: 4)
|
||||
let tostr(..args) = repr(args)
|
||||
test(tostr(a: 1, ..more, b: 2), "(a: 1, c: 3, d: 4, b: 2)")
|
||||
@ -69,14 +69,14 @@
|
||||
|
||||
---
|
||||
// Test spreading into array and dictionary.
|
||||
{
|
||||
#{
|
||||
let l = (1, 2, 3)
|
||||
let r = (5, 6, 7)
|
||||
test((..l, 4, ..r), range(1, 8))
|
||||
test((..none), ())
|
||||
}
|
||||
|
||||
{
|
||||
#{
|
||||
let x = (a: 1)
|
||||
let y = (b: 2)
|
||||
let z = (a: 3)
|
||||
@ -85,9 +85,9 @@
|
||||
}
|
||||
|
||||
---
|
||||
// Error: 11-17 cannot spread dictionary into array
|
||||
{(1, 2, ..(a: 1))}
|
||||
// Error: 12-18 cannot spread dictionary into array
|
||||
#{(1, 2, ..(a: 1))}
|
||||
|
||||
---
|
||||
// Error: 5-11 cannot spread array into dictionary
|
||||
{(..(1, 2), a: 1)}
|
||||
// Error: 6-12 cannot spread array into dictionary
|
||||
#{(..(1, 2), a: 1)}
|
||||
|
@ -13,12 +13,12 @@
|
||||
#test("🏳️🌈A🏳️⚧️".last(), "🏳️⚧️")
|
||||
|
||||
---
|
||||
// Error: 3-13 string is empty
|
||||
{ "".first() }
|
||||
// Error: 4-14 string is empty
|
||||
#{ "".first() }
|
||||
|
||||
---
|
||||
// Error: 3-12 string is empty
|
||||
{ "".last() }
|
||||
// Error: 4-13 string is empty
|
||||
#{ "".last() }
|
||||
|
||||
---
|
||||
// Test the `at` method.
|
||||
@ -27,8 +27,8 @@
|
||||
#test("Hey: 🏳️🌈 there!".at(5), "🏳️🌈")
|
||||
|
||||
---
|
||||
// Error: 3-16 string index out of bounds (index: 5, len: 5)
|
||||
{ "Hello".at(5) }
|
||||
// Error: 4-17 string index out of bounds (index: 5, len: 5)
|
||||
#{ "Hello".at(5) }
|
||||
|
||||
---
|
||||
// Test the `slice` method.
|
||||
@ -135,8 +135,8 @@
|
||||
#test("hello world".trim(regex(".")), "")
|
||||
|
||||
---
|
||||
// Error: 17-21 expected either `start` or `end`
|
||||
{"abc".trim(at: left)}
|
||||
// Error: 18-22 expected either `start` or `end`
|
||||
#{"abc".trim(at: left)}
|
||||
|
||||
---
|
||||
// Test the `split` method.
|
||||
|
@ -4,7 +4,7 @@
|
||||
// Should output `2 4 6 8 10`.
|
||||
#let i = 0
|
||||
#while i < 10 [
|
||||
{ i += 2 }
|
||||
#{ i += 2 }
|
||||
#i
|
||||
]
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
#test(while false {}, none)
|
||||
|
||||
#let i = 0
|
||||
#test(type(while i < 1 [{ i += 1 }]), "content")
|
||||
#test(type(while i < 1 [#{ i += 1 }]), "content")
|
||||
|
||||
---
|
||||
// Condition must be boolean.
|
||||
@ -38,7 +38,7 @@
|
||||
#while 2 < "hello".len() {}
|
||||
|
||||
---
|
||||
// Error: 2:1-2:24 loop seems to be infinite
|
||||
// Error: 2:2-2:24 loop seems to be infinite
|
||||
#let i = 1
|
||||
#while i > 0 { i += 1 }
|
||||
|
||||
@ -46,8 +46,8 @@
|
||||
// Error: 7 expected expression
|
||||
#while
|
||||
|
||||
// Error: 7 expected expression
|
||||
{while}
|
||||
// Error: 8 expected expression
|
||||
#{while}
|
||||
|
||||
// Error: 9 expected block
|
||||
#while x
|
||||
|
@ -43,8 +43,8 @@ Blue #move(dy: -0.15em)[🌊]
|
||||
```
|
||||
|
||||
---
|
||||
// Error: 7-19 cannot continue outside of loop
|
||||
#eval("{continue}")
|
||||
// Error: 7-18 cannot continue outside of loop
|
||||
#eval("#continue")
|
||||
|
||||
---
|
||||
// Error: 7-33 cannot access file system from here
|
||||
@ -72,5 +72,5 @@ _No relative giraffe!_
|
||||
```
|
||||
|
||||
---
|
||||
// Error: 7-16 expected comma
|
||||
#eval("{(1 2)}")
|
||||
// Error: 7-15 expected comma
|
||||
#eval("#(1 2)")
|
||||
|
@ -8,7 +8,7 @@
|
||||
// Test custom paragraphs with user code.
|
||||
#set text(8pt)
|
||||
|
||||
{
|
||||
#{
|
||||
let sentences = lorem(59)
|
||||
.split(".")
|
||||
.filter(s => s != "")
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
---
|
||||
// Set all margins at once.
|
||||
[
|
||||
#[
|
||||
#set page(height: 20pt, margin: 5pt)
|
||||
#place(top + left)[TL]
|
||||
#place(bottom + right)[BR]
|
||||
@ -11,10 +11,10 @@
|
||||
---
|
||||
// Set individual margins.
|
||||
#set page(height: 40pt)
|
||||
[#set page(margin: (left: 0pt)); #align(left)[Left]]
|
||||
[#set page(margin: (right: 0pt)); #align(right)[Right]]
|
||||
[#set page(margin: (top: 0pt)); #align(top)[Top]]
|
||||
[#set page(margin: (bottom: 0pt)); #align(bottom)[Bottom]]
|
||||
#[#set page(margin: (left: 0pt)); #align(left)[Left]]
|
||||
#[#set page(margin: (right: 0pt)); #align(right)[Right]]
|
||||
#[#set page(margin: (top: 0pt)); #align(top)[Top]]
|
||||
#[#set page(margin: (bottom: 0pt)); #align(bottom)[Bottom]]
|
||||
|
||||
// Ensure that specific margins override general margins.
|
||||
[#set page(margin: (rest: 0pt, left: 20pt)); Overriden]
|
||||
#[#set page(margin: (rest: 0pt, left: 20pt)); Overriden]
|
||||
|
@ -14,11 +14,11 @@
|
||||
// Set width and height.
|
||||
// Should result in one high and one wide page.
|
||||
#set page(width: 80pt, height: 80pt)
|
||||
[#set page(width: 40pt);High]
|
||||
[#set page(height: 40pt);Wide]
|
||||
#[#set page(width: 40pt);High]
|
||||
#[#set page(height: 40pt);Wide]
|
||||
|
||||
// Flipped predefined paper.
|
||||
[#set page(paper: "a11", flipped: true);Flipped A11]
|
||||
#[#set page(paper: "a11", flipped: true);Flipped A11]
|
||||
|
||||
---
|
||||
// Test page fill.
|
||||
|
@ -26,12 +26,12 @@ Second
|
||||
// Test a combination of pagebreaks, styled pages and pages with bodies.
|
||||
// Should result in three five pages, with the fourth one being forest-colored.
|
||||
#set page(width: 80pt, height: 30pt)
|
||||
[#set page(width: 60pt); First]
|
||||
#[#set page(width: 60pt); First]
|
||||
#pagebreak()
|
||||
#pagebreak()
|
||||
Third
|
||||
#page(height: 20pt, fill: forest)[]
|
||||
Fif[#set page();th]
|
||||
Fif#[#set page();th]
|
||||
|
||||
---
|
||||
// Test hard and weak pagebreak followed by page with body.
|
||||
|
@ -47,7 +47,7 @@ Lריווח #h(1cm) R
|
||||
|
||||
---
|
||||
// Test whether L1 whitespace resetting destroys stuff.
|
||||
الغالب #h(70pt) ن{" "}ة
|
||||
الغالب #h(70pt) ن#" "ة
|
||||
|
||||
---
|
||||
// Test setting a vertical direction.
|
||||
|
@ -30,4 +30,4 @@ D E F #linebreak(justify: true)
|
||||
// Test that there are no hick-ups with justification enabled and
|
||||
// basically empty paragraph.
|
||||
#set par(justify: true)
|
||||
{""}
|
||||
#""
|
||||
|
@ -12,7 +12,7 @@
|
||||
)
|
||||
|
||||
#for section in sections [
|
||||
{section.at(0)} #repeat[.] {section.at(1)} \
|
||||
#section.at(0) #repeat[.] #section.at(1) \
|
||||
]
|
||||
|
||||
---
|
||||
|
@ -3,13 +3,13 @@
|
||||
---
|
||||
// Test creating the TeX and XeTeX logos.
|
||||
#let size = 11pt
|
||||
#let tex = [{
|
||||
#let tex = {
|
||||
[T]
|
||||
h(-0.14 * size)
|
||||
move(dy: 0.22 * size)[E]
|
||||
h(-0.12 * size)
|
||||
[X]
|
||||
}]
|
||||
}
|
||||
|
||||
#let xetex = {
|
||||
[X]
|
||||
|
@ -3,10 +3,10 @@
|
||||
#let modifiers = (v => v, math.italic, math.bold, v => math.italic(math.bold(v)))
|
||||
|
||||
#let cells = ([:triangle:nested:], [--], [`italic`], [`bold`], [both])
|
||||
#for k in kinds {
|
||||
cells.push(raw(repr(k).trim("<function ").trim(">")))
|
||||
for m in modifiers {
|
||||
cells.push($ #m(#k(part)) $)
|
||||
#for kk in kinds {
|
||||
cells.push(raw(repr(kk).trim("<function ").trim(">")))
|
||||
for mm in modifiers {
|
||||
cells.push($ mm(kk(part)) $)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,23 +8,23 @@
|
||||
---
|
||||
Hello
|
||||
|
||||
// Error: 1-30 must appear before any content
|
||||
// Error: 2-30 must appear before any content
|
||||
#set document(title: "Hello")
|
||||
|
||||
---
|
||||
#box[
|
||||
// Error: 3-32 not allowed here
|
||||
// Error: 4-32 not allowed here
|
||||
#set document(title: "Hello")
|
||||
]
|
||||
|
||||
---
|
||||
#box[
|
||||
// Error: 3-18 not allowed here
|
||||
// Error: 4-18 not allowed here
|
||||
#set page("a4")
|
||||
]
|
||||
|
||||
---
|
||||
#box[
|
||||
// Error: 3-15 not allowed here
|
||||
// Error: 4-15 not allowed here
|
||||
#pagebreak()
|
||||
]
|
||||
|
@ -11,7 +11,7 @@
|
||||
= Analyse
|
||||
#lorem(10)
|
||||
|
||||
[
|
||||
#[
|
||||
#set heading(outlined: false)
|
||||
== Methodik
|
||||
#lorem(6)
|
||||
|
@ -3,10 +3,10 @@
|
||||
---
|
||||
#set text(size: 5pt)
|
||||
A // 5pt
|
||||
[
|
||||
#[
|
||||
#set text(size: 2em)
|
||||
B // 10pt
|
||||
[
|
||||
#[
|
||||
#set text(size: 1.5em + 1pt)
|
||||
C // 16pt
|
||||
#text(size: 2em)[D] // 32pt
|
||||
|
@ -8,7 +8,7 @@ _Emphasized and *strong* words!_
|
||||
hello_world Nutzer*innen
|
||||
|
||||
// Can contain paragraph in nested content block.
|
||||
_Still [
|
||||
_Still #[
|
||||
|
||||
] emphasized._
|
||||
|
||||
@ -24,7 +24,7 @@ Normal
|
||||
*Bold*
|
||||
|
||||
#set strong(delta: 150)
|
||||
*Medium* and *[*Bold*]*
|
||||
*Medium* and *#[*Bold*]*
|
||||
|
||||
---
|
||||
// Error: 13 expected underscore
|
||||
@ -38,6 +38,6 @@ _Hello
|
||||
World
|
||||
|
||||
---
|
||||
// Error: 25 expected star
|
||||
// Error: 25 expected underscore
|
||||
[_Cannot *be interleaved]
|
||||
// Error: 26 expected star
|
||||
// Error: 26 expected underscore
|
||||
#[_Cannot *be interleaved]
|
||||
|
@ -25,7 +25,7 @@
|
||||
Emoji: 🐪, 🌋, 🏞
|
||||
|
||||
// Colors.
|
||||
[
|
||||
#[
|
||||
#set text(fill: eastern)
|
||||
This is #text(rgb("FA644B"))[way more] colorful.
|
||||
]
|
||||
|
@ -50,4 +50,4 @@ He's told some books contain questionable "example text".
|
||||
// Test changing properties within text.
|
||||
"She suddenly started speaking french: #text(lang: "fr")['Je suis une banane.']" Roman told me.
|
||||
|
||||
Some people's thought on this would be [#set smartquote(enabled: false); "strange."]
|
||||
Some people's thought on this would be #[#set smartquote(enabled: false); "strange."]
|
||||
|
@ -7,7 +7,7 @@ C #let x = 2;D #test(x, 2) \
|
||||
E#if true [F]G \
|
||||
H #if true{"I"} J \
|
||||
K #if true [L] else []M \
|
||||
#let c = true; N#while c [{c = false}O] P \
|
||||
#let c = true; N#while c [#{c = false}O] P \
|
||||
#let c = true; Q #while c { c = false; "R" } S \
|
||||
T#for _ in (none,) {"U"}V
|
||||
|
||||
@ -19,11 +19,11 @@ A /**/B/**/ C
|
||||
|
||||
---
|
||||
// Test that a run consisting only of whitespace isn't trimmed.
|
||||
A[#set text("IBM Plex Serif"); ]B
|
||||
A#text("IBM Plex Serif")[ ]B
|
||||
|
||||
---
|
||||
// Test font change after space.
|
||||
Left [#set text("IBM Plex Serif");Right].
|
||||
Left #text("IBM Plex Serif")[Right].
|
||||
|
||||
---
|
||||
// Test that linebreak consumed surrounding spaces.
|
||||
@ -31,7 +31,7 @@ Left [#set text("IBM Plex Serif");Right].
|
||||
|
||||
---
|
||||
// Test that space at start of non-backslash-linebreak line isn't trimmed.
|
||||
A{"\n"} B
|
||||
A#"\n" B
|
||||
|
||||
---
|
||||
// Test that trailing space does not force a line break.
|
||||
|
@ -6,10 +6,10 @@
|
||||
|
||||
---
|
||||
// Test the `end` argument.
|
||||
{
|
||||
line(end: (10pt, 0pt))
|
||||
line(start: (0pt, 10pt), end: (0pt, 0pt))
|
||||
line(end: (15pt, 15pt))
|
||||
#{
|
||||
line(end: (10pt, 0pt))
|
||||
line(start: (0pt, 10pt), end: (0pt, 0pt))
|
||||
line(end: (15pt, 15pt))
|
||||
}
|
||||
#v(.5cm)
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
variant(fill: forest, stroke: black + 2pt),
|
||||
variant(fill: forest, stroke: conifer + 2pt),
|
||||
) {
|
||||
(align(horizon)[{i + 1}.], item, [])
|
||||
(align(horizon)[#{i + 1}.], item, [])
|
||||
}
|
||||
|
||||
#grid(
|
||||
|
@ -24,9 +24,9 @@
|
||||
#rect(height: 1cm, width: 100%, fill: rgb("734ced"))[Topleft]
|
||||
|
||||
// These are inline with text.
|
||||
\{#rect(width: 0.5in, height: 7pt, fill: rgb("d6cd67"))
|
||||
#rect(width: 0.5in, height: 7pt, fill: rgb("edd466"))
|
||||
#rect(width: 0.5in, height: 7pt, fill: rgb("e3be62"))\}
|
||||
{#rect(width: 0.5in, height: 7pt, fill: rgb("d6cd67"))
|
||||
#rect(width: 0.5in, height: 7pt, fill: rgb("edd466"))
|
||||
#rect(width: 0.5in, height: 7pt, fill: rgb("e3be62"))}
|
||||
|
||||
// Rounded corners.
|
||||
#rect(width: 2cm, radius: 60%)
|
||||
@ -39,10 +39,8 @@
|
||||
))
|
||||
|
||||
// Different strokes.
|
||||
[
|
||||
#set rect(stroke: (right: red))
|
||||
#rect(width: 100%, fill: lime, stroke: (x: 5pt, y: 1pt))
|
||||
]
|
||||
#set rect(stroke: (right: red))
|
||||
#rect(width: 100%, fill: lime, stroke: (x: 5pt, y: 1pt))
|
||||
|
||||
---
|
||||
// Error: 15-38 unexpected key "cake", valid keys are "top-left", "top-right", "bottom-right", "bottom-left", "left", "top", "right", "bottom", and "rest"
|
||||
|
@ -24,21 +24,7 @@
|
||||
},
|
||||
"common": {
|
||||
"patterns": [
|
||||
{ "include": "#comments" },
|
||||
{
|
||||
"name": "meta.block.code.typst",
|
||||
"begin": "{",
|
||||
"end": "}",
|
||||
"captures": { "0": { "name": "punctuation.definition.block.code.typst" } },
|
||||
"patterns": [{ "include": "#code" }]
|
||||
},
|
||||
{
|
||||
"name": "meta.block.content.typst",
|
||||
"begin": "\\[",
|
||||
"end": "\\]",
|
||||
"captures": { "0": { "name": "punctuation.definition.block.content.typst" } },
|
||||
"patterns": [{ "include": "#markup" }]
|
||||
}
|
||||
{ "include": "#comments" }
|
||||
]
|
||||
},
|
||||
"markup": {
|
||||
@ -213,14 +199,34 @@
|
||||
},
|
||||
{
|
||||
"name": "entity.other.interpolated.typst",
|
||||
"match": "(#)[[:alpha:]_][[:alnum:]_-]*",
|
||||
"match": "(#)[[:alpha:]_][.[:alnum:]_-]*",
|
||||
"captures": { "1": { "name": "punctuation.definition.variable.typst" } }
|
||||
},
|
||||
{
|
||||
"name": "meta.block.content.typst",
|
||||
"begin": "#",
|
||||
"end": "\\s",
|
||||
"patterns": [{ "include": "#code" }]
|
||||
}
|
||||
]
|
||||
},
|
||||
"code": {
|
||||
"patterns": [
|
||||
{ "include": "#common" },
|
||||
{
|
||||
"name": "meta.block.code.typst",
|
||||
"begin": "{",
|
||||
"end": "}",
|
||||
"captures": { "0": { "name": "punctuation.definition.block.code.typst" } },
|
||||
"patterns": [{ "include": "#code" }]
|
||||
},
|
||||
{
|
||||
"name": "meta.block.content.typst",
|
||||
"begin": "\\[",
|
||||
"end": "\\]",
|
||||
"captures": { "0": { "name": "punctuation.definition.block.content.typst" } },
|
||||
"patterns": [{ "include": "#markup" }]
|
||||
},
|
||||
{
|
||||
"name": "comment.line.double-slash.typst",
|
||||
"begin": "//",
|
||||
|
Loading…
x
Reference in New Issue
Block a user