summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/model/eval.rs310
-rw-r--r--src/model/library.rs2
-rw-r--r--src/model/value.rs9
-rw-r--r--src/syntax/ast.rs386
-rw-r--r--src/syntax/parsing.rs39
-rw-r--r--src/syntax/tokens.rs3
6 files changed, 359 insertions, 390 deletions
diff --git a/src/model/eval.rs b/src/model/eval.rs
index e9134114..959e4166 100644
--- a/src/model/eval.rs
+++ b/src/model/eval.rs
@@ -17,7 +17,7 @@ use crate::diag::{
use crate::geom::{Abs, Angle, Em, Fr, Ratio};
use crate::syntax::ast::AstNode;
use crate::syntax::{ast, Source, SourceId, Span, Spanned, SyntaxKind, SyntaxNode, Unit};
-use crate::util::{format_eco, EcoString, PathExt};
+use crate::util::{EcoString, PathExt};
use crate::World;
const MAX_ITERATIONS: usize = 10_000;
@@ -98,7 +98,7 @@ impl<'a> Vm<'a> {
}
/// Access the underlying world.
- pub fn world(&self) -> Tracked<dyn World> {
+ pub fn world(&self) -> Tracked<'a, dyn World> {
self.world
}
@@ -203,38 +203,38 @@ impl Eval for ast::Markup {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- eval_markup(vm, &mut self.children())
+ eval_markup(vm, &mut self.exprs())
}
}
-/// Evaluate a stream of markup nodes.
+/// Evaluate a stream of markup.
fn eval_markup(
vm: &mut Vm,
- nodes: &mut impl Iterator<Item = ast::MarkupNode>,
+ exprs: &mut impl Iterator<Item = ast::Expr>,
) -> SourceResult<Content> {
let flow = vm.flow.take();
- let mut seq = Vec::with_capacity(nodes.size_hint().1.unwrap_or_default());
+ let mut seq = Vec::with_capacity(exprs.size_hint().1.unwrap_or_default());
- while let Some(node) = nodes.next() {
- match node {
- ast::MarkupNode::Expr(ast::Expr::Set(set)) => {
+ while let Some(expr) = exprs.next() {
+ match expr {
+ ast::Expr::Set(set) => {
let styles = set.eval(vm)?;
if vm.flow.is_some() {
break;
}
- seq.push(eval_markup(vm, nodes)?.styled_with_map(styles))
+ seq.push(eval_markup(vm, exprs)?.styled_with_map(styles))
}
- ast::MarkupNode::Expr(ast::Expr::Show(show)) => {
+ ast::Expr::Show(show) => {
let recipe = show.eval(vm)?;
if vm.flow.is_some() {
break;
}
- let tail = eval_markup(vm, nodes)?;
+ let tail = eval_markup(vm, exprs)?;
seq.push(tail.styled_with_recipe(vm.world, recipe)?)
}
- ast::MarkupNode::Expr(expr) => match expr.eval(vm)? {
+ expr => match expr.eval(vm)? {
Value::Label(label) => {
if let Some(node) =
seq.iter_mut().rev().find(|node| node.labellable())
@@ -244,7 +244,6 @@ fn eval_markup(
}
value => seq.push(value.display().spanned(expr.span())),
},
- _ => seq.push(node.eval(vm)?),
}
if vm.flow.is_some() {
@@ -259,41 +258,96 @@ fn eval_markup(
Ok(Content::sequence(seq))
}
-impl Eval for ast::MarkupNode {
- type Output = Content;
+impl Eval for ast::Expr {
+ type Output = Value;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let forbidden = |name| {
+ error!(
+ self.span(),
+ "{} is only allowed directly in code and content blocks", name
+ )
+ };
+
+ match self {
+ Self::Space(v) => v.eval(vm).map(Value::Content),
+ Self::Linebreak(v) => v.eval(vm).map(Value::Content),
+ Self::Text(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::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),
+ Self::Link(v) => v.eval(vm).map(Value::Content),
+ Self::Raw(v) => v.eval(vm).map(Value::Content),
+ Self::Ref(v) => v.eval(vm).map(Value::Content),
+ Self::Heading(v) => v.eval(vm).map(Value::Content),
+ 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::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::Lit(v) => v.eval(vm),
+ Self::Ident(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),
+ Self::FieldAccess(v) => v.eval(vm),
+ Self::FuncCall(v) => v.eval(vm),
+ Self::MethodCall(v) => v.eval(vm),
+ Self::Closure(v) => v.eval(vm),
+ Self::Unary(v) => v.eval(vm),
+ Self::Binary(v) => v.eval(vm),
+ Self::Let(v) => v.eval(vm),
+ Self::Set(_) => bail!(forbidden("set")),
+ Self::Show(_) => bail!(forbidden("show")),
+ Self::Conditional(v) => v.eval(vm),
+ Self::While(v) => v.eval(vm),
+ Self::For(v) => v.eval(vm),
+ Self::Import(v) => v.eval(vm),
+ Self::Include(v) => v.eval(vm).map(Value::Content),
+ Self::Break(v) => v.eval(vm),
+ Self::Continue(v) => v.eval(vm),
+ Self::Return(v) => v.eval(vm),
+ }
+ }
+}
+
+impl ast::Expr {
+ fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
Ok(match self {
- Self::Space(v) => match v.newlines() {
- 0..=1 => (vm.items.space)(),
- _ => (vm.items.parbreak)(),
- },
- Self::Linebreak(v) => v.eval(vm)?,
- Self::Text(v) => v.eval(vm)?,
- Self::Escape(v) => (vm.items.text)(v.get().into()),
- Self::Shorthand(v) => v.eval(vm)?,
- Self::Symbol(v) => v.eval(vm)?,
- Self::SmartQuote(v) => v.eval(vm)?,
- Self::Strong(v) => v.eval(vm)?,
- Self::Emph(v) => v.eval(vm)?,
- Self::Link(v) => v.eval(vm)?,
- Self::Raw(v) => v.eval(vm)?,
- Self::Heading(v) => v.eval(vm)?,
- Self::List(v) => v.eval(vm)?,
- Self::Enum(v) => v.eval(vm)?,
- Self::Term(v) => v.eval(vm)?,
- Self::Ref(v) => v.eval(vm)?,
- Self::Expr(_) => unimplemented!("handled above"),
+ 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.eval(vm)?.display_in_math(),
}
.spanned(self.span()))
}
}
+impl Eval for ast::Space {
+ type Output = Content;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(match self.newlines() {
+ 0..=1 => (vm.items.space)(),
+ _ => (vm.items.parbreak)(),
+ })
+ }
+}
+
impl Eval for ast::Linebreak {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items.linebreak)(false))
+ Ok((vm.items.linebreak)())
}
}
@@ -305,6 +359,20 @@ impl Eval for ast::Text {
}
}
+impl Eval for ast::Escape {
+ type Output = Content;
+
+ 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()))
+ }
+}
+
impl Eval for ast::Shorthand {
type Output = Content;
@@ -313,6 +381,12 @@ impl Eval for ast::Shorthand {
}
}
+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;
@@ -321,6 +395,12 @@ impl Eval for ast::Symbol {
}
}
+impl ast::Symbol {
+ fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
+ Ok((vm.items.symbol)(self.get().clone() + ":op".into()))
+ }
+}
+
impl Eval for ast::SmartQuote {
type Output = Content;
@@ -414,48 +494,12 @@ impl Eval for ast::Math {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items.math)(
- self.children()
- .map(|node| node.eval(vm))
- .collect::<SourceResult<_>>()?,
- self.block(),
- ))
- }
-}
-
-impl Eval for ast::MathNode {
- type Output = Content;
-
- fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok(match self {
- Self::Space(_) => (vm.items.space)(),
- Self::Linebreak(v) => v.eval(vm)?,
- Self::Escape(v) => (vm.items.math_atom)(v.get().into()),
- Self::Shorthand(v) => (vm.items.math_atom)(v.get().into()),
- Self::Atom(v) => v.eval(vm)?,
- Self::Symbol(v) => (vm.items.symbol)(v.get().clone() + ":op".into()),
- Self::Script(v) => v.eval(vm)?,
- Self::Frac(v) => v.eval(vm)?,
- Self::AlignPoint(v) => v.eval(vm)?,
- Self::Group(v) => v.eval(vm)?,
- Self::Expr(v) => {
- if let ast::Expr::Ident(ident) = v {
- if self.as_untyped().len() == ident.len()
- && matches!(vm.scopes.get(ident), Ok(Value::Func(_)) | Err(_))
- {
- let node = (vm.items.symbol)(ident.get().clone() + ":op".into());
- return Ok(node.spanned(self.span()));
- }
- }
-
- match v.eval(vm)? {
- Value::Int(v) => (vm.items.math_atom)(format_eco!("{}", v)),
- Value::Float(v) => (vm.items.math_atom)(format_eco!("{}", v)),
- v => v.display(),
- }
- }
- }
- .spanned(self.span()))
+ let seq = self
+ .exprs()
+ .map(|expr| expr.eval_in_math(vm))
+ .collect::<SourceResult<_>>()?;
+ let block = self.block();
+ Ok((vm.items.math)(seq, block))
}
}
@@ -471,11 +515,10 @@ impl Eval for ast::Script {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items.math_script)(
- self.base().eval(vm)?,
- self.sub().map(|node| node.eval(vm)).transpose()?,
- self.sup().map(|node| node.eval(vm)).transpose()?,
- ))
+ let base = self.base().eval_in_math(vm)?;
+ let sub = self.sub().map(|expr| expr.eval_in_math(vm)).transpose()?;
+ let sup = self.sup().map(|expr| expr.eval_in_math(vm)).transpose()?;
+ Ok((vm.items.math_script)(base, sub, sup))
}
}
@@ -483,7 +526,9 @@ impl Eval for ast::Frac {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items.math_frac)(self.num().eval(vm)?, self.denom().eval(vm)?))
+ let num = self.num().eval_in_math(vm)?;
+ let denom = self.denom().eval_in_math(vm)?;
+ Ok((vm.items.math_frac)(num, denom))
}
}
@@ -495,47 +540,6 @@ impl Eval for ast::AlignPoint {
}
}
-impl Eval for ast::Expr {
- type Output = Value;
-
- fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- let forbidden = |name| {
- error!(
- self.span(),
- "{} is only allowed directly in code and content blocks", name
- )
- };
-
- match self {
- Self::Lit(v) => v.eval(vm),
- Self::Ident(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),
- Self::FieldAccess(v) => v.eval(vm),
- Self::FuncCall(v) => v.eval(vm),
- Self::MethodCall(v) => v.eval(vm),
- Self::Closure(v) => v.eval(vm),
- Self::Unary(v) => v.eval(vm),
- Self::Binary(v) => v.eval(vm),
- Self::Let(v) => v.eval(vm),
- Self::Set(_) => bail!(forbidden("set")),
- Self::Show(_) => bail!(forbidden("show")),
- Self::Conditional(v) => v.eval(vm),
- Self::While(v) => v.eval(vm),
- Self::For(v) => v.eval(vm),
- Self::Import(v) => v.eval(vm),
- Self::Include(v) => v.eval(vm).map(Value::Content),
- Self::Break(v) => v.eval(vm),
- Self::Continue(v) => v.eval(vm),
- Self::Return(v) => v.eval(vm),
- }
- }
-}
-
impl Eval for ast::Lit {
type Output = Value;
@@ -571,6 +575,18 @@ impl Eval for ast::Ident {
}
}
+impl ast::Ident {
+ fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
+ if self.as_untyped().len() == self.len()
+ && matches!(vm.scopes.get(&self), Ok(Value::Func(_)) | Err(_))
+ {
+ Ok((vm.items.symbol)(self.get().clone() + ":op".into()))
+ } else {
+ Ok(self.eval(vm)?.display_in_math())
+ }
+ }
+}
+
impl Eval for ast::CodeBlock {
type Output = Value;
@@ -789,7 +805,11 @@ impl Eval for ast::FieldAccess {
.field(&field)
.ok_or_else(|| format!("unknown field {field:?}"))
.at(span)?,
- v => bail!(self.target().span(), "cannot access field on {}", v.type_name()),
+ v => bail!(
+ self.target().span(),
+ "expected dictionary or content, found {}",
+ v.type_name()
+ ),
})
}
}
@@ -916,7 +936,7 @@ impl Eval for ast::Closure {
}
}
- // Define the closure function.
+ // Define the closure.
let closure = Closure {
location: vm.location,
name,
@@ -966,7 +986,7 @@ impl Eval for ast::ShowRule {
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let selector = self
.selector()
- .map(|selector| selector.eval(vm)?.cast::<Selector>().at(selector.span()))
+ .map(|sel| sel.eval(vm)?.cast::<Selector>().at(sel.span()))
.transpose()?;
let transform = self.transform();
@@ -1094,7 +1114,6 @@ impl Eval for ast::ForLoop {
}
let iter = self.iter().eval(vm)?;
-
let pattern = self.pattern();
let key = pattern.key().map(ast::Ident::take);
let value = pattern.value().take();
@@ -1266,28 +1285,35 @@ impl Access for ast::Parenthesized {
impl Access for ast::FieldAccess {
fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
- Ok(match self.target().access(vm)? {
- Value::Dict(dict) => dict.at_mut(self.field().take().into()),
- v => bail!(
+ let value = self.target().access(vm)?;
+ let Value::Dict(dict) = value else {
+ bail!(
self.target().span(),
"expected dictionary, found {}",
- v.type_name(),
- ),
- })
+ value.type_name(),
+ );
+ };
+
+ Ok(dict.at_mut(self.field().take().into()))
}
}
impl Access for ast::MethodCall {
fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
let span = self.span();
- let method = self.method();
- let args = self.args().eval(vm)?;
- if methods::is_accessor(&method) {
- let value = self.target().access(vm)?;
- methods::call_access(value, &method, args, span)
- } else {
+ let method = self.method().take();
+ let world = vm.world();
+
+ if !methods::is_accessor(&method) {
let _ = self.eval(vm)?;
bail!(span, "cannot mutate a temporary value");
}
+
+ let args = self.args().eval(vm)?;
+ let value = self.target().access(vm)?;
+ let result = methods::call_access(value, &method, args, span);
+
+ let point = || Tracepoint::Call(Some(method.clone()));
+ result.trace(world, point, span)
}
}
diff --git a/src/model/library.rs b/src/model/library.rs
index c6449e27..5360b00a 100644
--- a/src/model/library.rs
+++ b/src/model/library.rs
@@ -34,7 +34,7 @@ pub struct LangItems {
/// Whitespace.
pub space: fn() -> Content,
/// A forced line break: `\`.
- pub linebreak: fn(justify: bool) -> Content,
+ pub linebreak: fn() -> Content,
/// Plain text without markup.
pub text: fn(text: EcoString) -> Content,
/// The id of the text node.
diff --git a/src/model/value.rs b/src/model/value.rs
index 1c687d8d..0716985d 100644
--- a/src/model/value.rs
+++ b/src/model/value.rs
@@ -112,6 +112,15 @@ impl Value {
_ => item!(raw)(self.repr().into(), Some("typc".into()), false),
}
}
+
+ /// Return the display representation of the value in math mode.
+ pub fn display_in_math(self) -> Content {
+ match self {
+ Self::Int(v) => item!(math_atom)(format_eco!("{}", v)),
+ Self::Float(v) => item!(math_atom)(format_eco!("{}", v)),
+ _ => self.display(),
+ }
+ }
}
impl Default for Value {
diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs
index 6483f7cc..ccad77c2 100644
--- a/src/syntax/ast.rs
+++ b/src/syntax/ast.rs
@@ -1,6 +1,6 @@
//! A typed layer over the untyped syntax tree.
//!
-//! The AST is rooted in the [`MarkupNode`].
+//! The AST is rooted in the [`Markup`] node.
use std::num::NonZeroUsize;
use std::ops::Deref;
@@ -54,26 +54,26 @@ node! {
}
impl Markup {
- /// The children.
- pub fn children(&self) -> impl DoubleEndedIterator<Item = MarkupNode> + '_ {
+ /// The expressions.
+ pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ {
let mut was_stmt = false;
self.0
.children()
.filter(move |node| {
- // Ignore linebreak directly after statements without semicolons.
+ // Ignore newline directly after statements without semicolons.
let kind = node.kind();
let keep =
!was_stmt || !matches!(kind, SyntaxKind::Space { newlines: 1 });
was_stmt = kind.is_stmt();
keep
})
- .filter_map(SyntaxNode::cast)
+ .filter_map(Expr::cast_with_space)
}
}
-/// A single piece of markup.
-#[derive(Debug, Clone, PartialEq)]
-pub enum MarkupNode {
+/// An expression in markup, math or code.
+#[derive(Debug, Clone, PartialEq, Hash)]
+pub enum Expr {
/// Whitespace.
Space(Space),
/// A forced line break: `\`.
@@ -107,14 +107,78 @@ pub enum MarkupNode {
Enum(EnumItem),
/// An item in a term list: `/ Term: Details`.
Term(TermItem),
- /// An expression.
- Expr(Expr),
+ /// A math formula: `$x$`, `$ x^2 $`.
+ Math(Math),
+ /// An atom in a math formula: `x`, `+`, `12`.
+ Atom(Atom),
+ /// A base with optional sub- and superscripts in a math formula: `a_1^2`.
+ Script(Script),
+ /// A fraction in a math formula: `x/2`.
+ Frac(Frac),
+ /// An alignment point in a math formula: `&`, `&&`.
+ AlignPoint(AlignPoint),
+ /// A literal: `1`, `true`, ...
+ Lit(Lit),
+ /// An identifier: `left`.
+ Ident(Ident),
+ /// A code block: `{ let x = 1; x + 2 }`.
+ Code(CodeBlock),
+ /// A content block: `[*Hi* there!]`.
+ Content(ContentBlock),
+ /// A grouped expression: `(1 + 2)`.
+ Parenthesized(Parenthesized),
+ /// An array: `(1, "hi", 12cm)`.
+ Array(Array),
+ /// A dictionary: `(thickness: 3pt, pattern: dashed)`.
+ Dict(Dict),
+ /// A unary operation: `-x`.
+ Unary(Unary),
+ /// A binary operation: `a + b`.
+ Binary(Binary),
+ /// A field access: `properties.age`.
+ FieldAccess(FieldAccess),
+ /// An invocation of a function: `f(x, y)`.
+ FuncCall(FuncCall),
+ /// An invocation of a method: `array.push(v)`.
+ MethodCall(MethodCall),
+ /// A closure: `(x, y) => z`.
+ Closure(Closure),
+ /// A let binding: `let x = 1`.
+ Let(LetBinding),
+ /// A set rule: `set text(...)`.
+ Set(SetRule),
+ /// A show rule: `show heading: it => [*{it.body}*]`.
+ Show(ShowRule),
+ /// An if-else conditional: `if x { y } else { z }`.
+ Conditional(Conditional),
+ /// A while loop: `while x { y }`.
+ While(WhileLoop),
+ /// A for loop: `for x in y { z }`.
+ For(ForLoop),
+ /// A module import: `import a, b, c from "utils.typ"`.
+ Import(ModuleImport),
+ /// A module include: `include "chapter1.typ"`.
+ Include(ModuleInclude),
+ /// A break from a loop: `break`.
+ Break(LoopBreak),
+ /// A continue in a loop: `continue`.
+ Continue(LoopContinue),
+ /// A return from a function: `return`, `return x + 1`.
+ Return(FuncReturn),
}
-impl AstNode for MarkupNode {
- fn from_untyped(node: &SyntaxNode) -> Option<Self> {
+impl Expr {
+ fn cast_with_space(node: &SyntaxNode) -> Option<Self> {
match node.kind() {
SyntaxKind::Space { .. } => node.cast().map(Self::Space),
+ _ => Self::from_untyped(node),
+ }
+ }
+}
+
+impl AstNode for Expr {
+ fn from_untyped(node: &SyntaxNode) -> Option<Self> {
+ match node.kind() {
SyntaxKind::Linebreak => node.cast().map(Self::Linebreak),
SyntaxKind::Text(_) => node.cast().map(Self::Text),
SyntaxKind::Escape(_) => node.cast().map(Self::Escape),
@@ -130,7 +194,35 @@ impl AstNode for MarkupNode {
SyntaxKind::ListItem => node.cast().map(Self::List),
SyntaxKind::EnumItem => node.cast().map(Self::Enum),
SyntaxKind::TermItem => node.cast().map(Self::Term),
- _ => node.cast().map(Self::Expr),
+ SyntaxKind::Math => node.cast().map(Self::Math),
+ SyntaxKind::Atom(_) => node.cast().map(Self::Atom),
+ SyntaxKind::Script => node.cast().map(Self::Script),
+ SyntaxKind::Frac => node.cast().map(Self::Frac),
+ SyntaxKind::AlignPoint => node.cast().map(Self::AlignPoint),
+ SyntaxKind::Ident(_) => node.cast().map(Self::Ident),
+ SyntaxKind::CodeBlock => node.cast().map(Self::Code),
+ SyntaxKind::ContentBlock => node.cast().map(Self::Content),
+ SyntaxKind::Parenthesized => node.cast().map(Self::Parenthesized),
+ SyntaxKind::Array => node.cast().map(Self::Array),
+ SyntaxKind::Dict => node.cast().map(Self::Dict),
+ SyntaxKind::Unary => node.cast().map(Self::Unary),
+ SyntaxKind::Binary => node.cast().map(Self::Binary),
+ SyntaxKind::FieldAccess => node.cast().map(Self::FieldAccess),
+ SyntaxKind::FuncCall => node.cast().map(Self::FuncCall),
+ SyntaxKind::MethodCall => node.cast().map(Self::MethodCall),
+ SyntaxKind::Closure => node.cast().map(Self::Closure),
+ SyntaxKind::LetBinding => node.cast().map(Self::Let),
+ SyntaxKind::SetRule => node.cast().map(Self::Set),
+ SyntaxKind::ShowRule => node.cast().map(Self::Show),
+ SyntaxKind::Conditional => node.cast().map(Self::Conditional),
+ SyntaxKind::WhileLoop => node.cast().map(Self::While),
+ SyntaxKind::ForLoop => node.cast().map(Self::For),
+ SyntaxKind::ModuleImport => node.cast().map(Self::Import),
+ SyntaxKind::ModuleInclude => node.cast().map(Self::Include),
+ SyntaxKind::LoopBreak => node.cast().map(Self::Break),
+ SyntaxKind::LoopContinue => node.cast().map(Self::Continue),
+ SyntaxKind::FuncReturn => node.cast().map(Self::Return),
+ _ => node.cast().map(Self::Lit),
}
}
@@ -152,11 +244,58 @@ impl AstNode for MarkupNode {
Self::List(v) => v.as_untyped(),
Self::Enum(v) => v.as_untyped(),
Self::Term(v) => v.as_untyped(),
- Self::Expr(v) => v.as_untyped(),
+ Self::Math(v) => v.as_untyped(),
+ Self::Atom(v) => v.as_untyped(),
+ Self::Script(v) => v.as_untyped(),
+ Self::Frac(v) => v.as_untyped(),
+ Self::AlignPoint(v) => v.as_untyped(),
+ Self::Lit(v) => v.as_untyped(),
+ Self::Code(v) => v.as_untyped(),
+ Self::Content(v) => v.as_untyped(),
+ Self::Ident(v) => v.as_untyped(),
+ Self::Array(v) => v.as_untyped(),
+ Self::Dict(v) => v.as_untyped(),
+ Self::Parenthesized(v) => v.as_untyped(),
+ Self::Unary(v) => v.as_untyped(),
+ Self::Binary(v) => v.as_untyped(),
+ Self::FieldAccess(v) => v.as_untyped(),
+ Self::FuncCall(v) => v.as_untyped(),
+ Self::MethodCall(v) => v.as_untyped(),
+ Self::Closure(v) => v.as_untyped(),
+ Self::Let(v) => v.as_untyped(),
+ Self::Set(v) => v.as_untyped(),
+ Self::Show(v) => v.as_untyped(),
+ Self::Conditional(v) => v.as_untyped(),
+ Self::While(v) => v.as_untyped(),
+ Self::For(v) => v.as_untyped(),
+ Self::Import(v) => v.as_untyped(),
+ Self::Include(v) => v.as_untyped(),
+ Self::Break(v) => v.as_untyped(),
+ Self::Continue(v) => v.as_untyped(),
+ Self::Return(v) => v.as_untyped(),
}
}
}
+impl Expr {
+ /// Whether the expression can be shortened in markup with a hashtag.
+ pub fn has_short_form(&self) -> bool {
+ matches!(
+ self,
+ Self::Ident(_)
+ | Self::FuncCall(_)
+ | Self::Let(_)
+ | Self::Set(_)
+ | Self::Show(_)
+ | Self::Conditional(_)
+ | Self::While(_)
+ | Self::For(_)
+ | Self::Import(_)
+ | Self::Include(_)
+ )
+ }
+}
+
node! {
/// Whitespace.
Space
@@ -418,78 +557,15 @@ node! {
}
impl Math {
- /// The children.
- pub fn children(&self) -> impl DoubleEndedIterator<Item = MathNode> + '_ {
- self.0.children().filter_map(SyntaxNode::cast)
+ /// The expressions the formula consists of.
+ pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ {
+ self.0.children().filter_map(Expr::cast_with_space)
}
/// Whether the formula should be displayed as a separate block.
pub fn block(&self) -> bool {
- matches!(self.children().next(), Some(MathNode::Space(_)))
- && matches!(self.children().last(), Some(MathNode::Space(_)))
- }
-}
-
-/// A single piece of a math formula.
-#[derive(Debug, Clone, PartialEq, Hash)]
-pub enum MathNode {
- /// Whitespace.
- Space(Space),
- /// A forced line break: `\`.
- Linebreak(Linebreak),
- /// An escape sequence: `\#`, `\u{1F5FA}`.
- Escape(Escape),
- /// A shorthand for a unicode codepoint. For example, `->` for a right
- /// arrow.
- Shorthand(Shorthand),
- /// An atom: `x`, `+`, `12`.
- Atom(Atom),
- /// Symbol notation: `:arrow:l:` or `arrow:l`. Notations without any colons
- /// are parsed as identifier expression and handled during evaluation.
- Symbol(Symbol),
- /// A base with optional sub- and superscripts: `a_1^2`.
- Script(Script),
- /// A fraction: `x/2`.
- Frac(Frac),
- /// An alignment point: `&`, `&&`.
- AlignPoint(AlignPoint),
- /// Grouped mathematical material.
- Group(Math),
- /// An expression.
- Expr(Expr),
-}
-
-impl AstNode for MathNode {
- fn from_untyped(node: &SyntaxNode) -> Option<Self> {
- match node.kind() {
- SyntaxKind::Space { .. } => node.cast().map(Self::Space),
- SyntaxKind::Linebreak => node.cast().map(Self::Linebreak),
- SyntaxKind::Escape(_) => node.cast().map(Self::Escape),
- SyntaxKind::Shorthand(_) => node.cast().map(Self::Shorthand),
- SyntaxKind::Atom(_) => node.cast().map(Self::Atom),
- SyntaxKind::Symbol(_) => node.cast().map(Self::Symbol),
- SyntaxKind::Script => node.cast().map(Self::Script),
- SyntaxKind::Frac => node.cast().map(Self::Frac),
- SyntaxKind::AlignPoint => node.cast().map(Self::AlignPoint),
- SyntaxKind::Math => node.cast().map(Self::Group),
- _ => node.cast().map(Self::Expr),
- }
- }
-
- fn as_untyped(&self) -> &SyntaxNode {
- match self {
- Self::Space(v) => v.as_untyped(),
- Self::Linebreak(v) => v.as_untyped(),
- Self::Escape(v) => v.as_untyped(),
- Self::Shorthand(v) => v.as_untyped(),
- Self::Atom(v) => v.as_untyped(),
- Self::Symbol(v) => v.as_untyped(),
- Self::Script(v) => v.as_untyped(),
- Self::Frac(v) => v.as_untyped(),
- Self::AlignPoint(v) => v.as_untyped(),
- Self::Group(v) => v.as_untyped(),
- Self::Expr(v) => v.as_untyped(),
- }
+ matches!(self.exprs().next(), Some(Expr::Space(_)))
+ && matches!(self.exprs().last(), Some(Expr::Space(_)))
}
}
@@ -515,12 +591,12 @@ node! {
impl Script {
/// The base of the script.
- pub fn base(&self) -> MathNode {
+ pub fn base(&self) -> Expr {
self.0.cast_first_child().expect("script node is missing base")
}
/// The subscript.
- pub fn sub(&self) -> Option<MathNode> {
+ pub fn sub(&self) -> Option<Expr> {
self.0
.children()
.skip_while(|node| !matches!(node.kind(), SyntaxKind::Underscore))
@@ -529,7 +605,7 @@ impl Script {
}
/// The superscript.
- pub fn sup(&self) -> Option<MathNode> {
+ pub fn sup(&self) -> Option<Expr> {
self.0
.children()
.skip_while(|node| !matches!(node.kind(), SyntaxKind::Hat))
@@ -545,12 +621,12 @@ node! {
impl Frac {
/// The numerator.
- pub fn num(&self) -> MathNode {
+ pub fn num(&self) -> Expr {
self.0.cast_first_child().expect("fraction is missing numerator")
}
/// The denominator.
- pub fn denom(&self) -> MathNode {
+ pub fn denom(&self) -> Expr {
self.0.cast_last_child().expect("fraction is missing denominator")
}
}
@@ -572,142 +648,6 @@ impl AlignPoint {
}
}
-/// An expression.
-#[derive(Debug, Clone, PartialEq, Hash)]
-pub enum Expr {
- /// A literal: `1`, `true`, ...
- Lit(Lit),
- /// An identifier: `left`.
- Ident(Ident),
- /// A code block: `{ let x = 1; x + 2 }`.
- Code(CodeBlock),
- /// A content block: `[*Hi* there!]`.
- Content(ContentBlock),
- /// A math formula: `$x$`, `$ x^2 $`.
- Math(Math),
- /// A grouped expression: `(1 + 2)`.
- Parenthesized(Parenthesized),
- /// An array: `(1, "hi", 12cm)`.
- Array(Array),
- /// A dictionary: `(thickness: 3pt, pattern: dashed)`.
- Dict(Dict),
- /// A unary operation: `-x`.
- Unary(Unary),
- /// A binary operation: `a + b`.
- Binary(Binary),
- /// A field access: `properties.age`.
- FieldAccess(FieldAccess),
- /// An invocation of a function: `f(x, y)`.
- FuncCall(FuncCall),
- /// An invocation of a method: `array.push(v)`.
- MethodCall(MethodCall),
- /// A closure: `(x, y) => z`.
- Closure(Closure),
- /// A let binding: `let x = 1`.
- Let(LetBinding),
- /// A set rule: `set text(...)`.
- Set(SetRule),
- /// A show rule: `show heading: it => [*{it.body}*]`.
- Show(ShowRule),
- /// An if-else conditional: `if x { y } else { z }`.
- Conditional(Conditional),
- /// A while loop: `while x { y }`.
- While(WhileLoop),
- /// A for loop: `for x in y { z }`.
- For(ForLoop),
- /// A module import: `import a, b, c from "utils.typ"`.
- Import(ModuleImport),
- /// A module include: `include "chapter1.typ"`.
- Include(ModuleInclude),
- /// A break from a loop: `break`.
- Break(LoopBreak),
- /// A continue in a loop: `continue`.
- Continue(LoopContinue),
- /// A return from a function: `return`, `return x + 1`.
- Return(FuncReturn),
-}
-
-impl AstNode for Expr {
- fn from_untyped(node: &SyntaxNode) -> Option<Self> {
- match node.kind() {
- SyntaxKind::Ident(_) => node.cast().map(Self::Ident),
- SyntaxKind::CodeBlock => node.cast().map(Self::Code),
- SyntaxKind::ContentBlock => node.cast().map(Self::Content),
- SyntaxKind::Math => node.cast().map(Self::Math),
- SyntaxKind::Parenthesized => node.cast().map(Self::Parenthesized),
- SyntaxKind::Array => node.cast().map(Self::Array),
- SyntaxKind::Dict => node.cast().map(Self::Dict),
- SyntaxKind::Unary => node.cast().map(Self::Unary),
- SyntaxKind::Binary => node.cast().map(Self::Binary),
- SyntaxKind::FieldAccess => node.cast().map(Self::FieldAccess),
- SyntaxKind::FuncCall => node.cast().map(Self::FuncCall),
- SyntaxKind::MethodCall => node.cast().map(Self::MethodCall),
- SyntaxKind::Closure => node.cast().map(Self::Closure),
- SyntaxKind::LetBinding => node.cast().map(Self::Let),
- SyntaxKind::SetRule => node.cast().map(Self::Set),
- SyntaxKind::ShowRule => node.cast().map(Self::Show),
- SyntaxKind::Conditional => node.cast().map(Self::Conditional),
- SyntaxKind::WhileLoop => node.cast().map(Self::While),
- SyntaxKind::ForLoop => node.cast().map(Self::For),
- SyntaxKind::ModuleImport => node.cast().map(Self::Import),
- SyntaxKind::ModuleInclude => node.cast().map(Self::Include),
- SyntaxKind::LoopBreak => node.cast().map(Self::Break),
- SyntaxKind::LoopContinue => node.cast().map(Self::Continue),
- SyntaxKind::FuncReturn => node.cast().map(Self::Return),
- _ => node.cast().map(Self::Lit),
- }
- }
-
- fn as_untyped(&self) -> &SyntaxNode {
- match self {
- Self::Lit(v) => v.as_untyped(),
- Self::Code(v) => v.as_untyped(),
- Self::Content(v) => v.as_untyped(),
- Self::Math(v) => v.as_untyped(),
- Self::Ident(v) => v.as_untyped(),
- Self::Array(v) => v.as_untyped(),
- Self::Dict(v) => v.as_untyped(),
- Self::Parenthesized(v) => v.as_untyped(),
- Self::Unary(v) => v.as_untyped(),
- Self::Binary(v) => v.as_untyped(),
- Self::FieldAccess(v) => v.as_untyped(),
- Self::FuncCall(v) => v.as_untyped(),
- Self::MethodCall(v) => v.as_untyped(),
- Self::Closure(v) => v.as_untyped(),
- Self::Let(v) => v.as_untyped(),
- Self::Set(v) => v.as_untyped(),
- Self::Show(v) => v.as_untyped(),
- Self::Conditional(v) => v.as_untyped(),
- Self::While(v) => v.as_untyped(),
- Self::For(v) => v.as_untyped(),
- Self::Import(v) => v.as_untyped(),
- Self::Include(v) => v.as_untyped(),
- Self::Break(v) => v.as_untyped(),
- Self::Continue(v) => v.as_untyped(),
- Self::Return(v) => v.as_untyped(),
- }
- }
-}
-
-impl Expr {
- /// Whether the expression can be shortened in markup with a hashtag.
- pub fn has_short_form(&self) -> bool {
- matches!(
- self,
- Self::Ident(_)
- | Self::FuncCall(_)
- | Self::Let(_)
- | Self::Set(_)
- | Self::Show(_)
- | Self::Conditional(_)
- | Self::While(_)
- | Self::For(_)
- | Self::Import(_)
- | Self::Include(_)
- )
- }
-}
-
node! {
/// A literal: `1`, `true`, ...
Lit: SyntaxKind::None
diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs
index e0405407..900e0e67 100644
--- a/src/syntax/parsing.rs
+++ b/src/syntax/parsing.rs
@@ -586,11 +586,23 @@ fn expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) -> ParseResult {
}
fn primary(p: &mut Parser, atomic: bool) -> ParseResult {
- if literal(p) {
- return Ok(());
- }
-
match p.peek() {
+ // Literals and few other things.
+ Some(
+ SyntaxKind::None
+ | SyntaxKind::Auto
+ | SyntaxKind::Int(_)
+ | SyntaxKind::Float(_)
+ | SyntaxKind::Bool(_)
+ | SyntaxKind::Numeric(_, _)
+ | SyntaxKind::Str(_)
+ | SyntaxKind::Label(_)
+ | SyntaxKind::Raw(_),
+ ) => {
+ p.eat();
+ Ok(())
+ }
+
// Things that start with an identifier.
Some(SyntaxKind::Ident(_)) => {
let marker = p.marker();
@@ -638,25 +650,6 @@ fn primary(p: &mut Parser, atomic: bool) -> ParseResult {
}
}
-fn literal(p: &mut Parser) -> bool {
- match p.peek() {
- Some(
- SyntaxKind::None
- | SyntaxKind::Auto
- | SyntaxKind::Int(_)
- | SyntaxKind::Float(_)
- | SyntaxKind::Bool(_)
- | SyntaxKind::Numeric(_, _)
- | SyntaxKind::Str(_)
- | SyntaxKind::Label(_),
- ) => {
- p.eat();
- true
- }
- _ => false,
- }
-}
-
fn ident(p: &mut Parser) -> ParseResult {
match p.peek() {
Some(SyntaxKind::Ident(_)) => {
diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs
index 98f244e1..98b6d8a8 100644
--- a/src/syntax/tokens.rs
+++ b/src/syntax/tokens.rs
@@ -523,8 +523,9 @@ impl Tokens<'_> {
// Math.
'$' => SyntaxKind::Dollar,
- // Labels.
+ // Labels and raw.
'<' if self.s.at(is_id_continue) => self.label(),
+ '`' => self.raw(),
// Two-char operators.
'=' if self.s.eat_if('=') => SyntaxKind::EqEq,