diff --git a/library/src/lib.rs b/library/src/lib.rs index e345bbeef..14b020339 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -180,7 +180,7 @@ fn items() -> LangItems { em: |styles| styles.get(text::TextNode::SIZE), dir: |styles| styles.get(text::TextNode::DIR), space: || text::SpaceNode.pack(), - linebreak: |justify| text::LinebreakNode { justify }.pack(), + linebreak: || text::LinebreakNode { justify: false }.pack(), text: |text| text::TextNode(text).pack(), text_id: NodeId::of::(), text_str: |content| Some(&content.to::()?.0), diff --git a/src/model/eval.rs b/src/model/eval.rs index e9134114e..959e41667 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 { + 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 { - 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, + exprs: &mut impl Iterator, ) -> SourceResult { 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,33 +258,88 @@ fn eval_markup( Ok(Content::sequence(seq)) } -impl Eval for ast::MarkupNode { +impl Eval for ast::Expr { + type Output = Value; + + fn eval(&self, vm: &mut Vm) -> SourceResult { + 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 { + 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.eval(vm)?.display_in_math(), + } + .spanned(self.span())) + } +} + +impl Eval for ast::Space { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult { - 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"), - } - .spanned(self.span())) + Ok(match self.newlines() { + 0..=1 => (vm.items.space)(), + _ => (vm.items.parbreak)(), + }) } } @@ -293,7 +347,7 @@ impl Eval for ast::Linebreak { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult { - 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 { + Ok((vm.items.text)(self.get().into())) + } +} + +impl ast::Escape { + fn eval_in_math(&self, vm: &mut Vm) -> SourceResult { + 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 { + 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 { + 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 { - Ok((vm.items.math)( - self.children() - .map(|node| node.eval(vm)) - .collect::>()?, - self.block(), - )) - } -} - -impl Eval for ast::MathNode { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult { - 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::>()?; + 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 { - 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 { - 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 { - 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 { + 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 { let selector = self .selector() - .map(|selector| selector.eval(vm)?.cast::().at(selector.span())) + .map(|sel| sel.eval(vm)?.cast::().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 c6449e278..5360b00aa 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 1c687d8dc..0716985de 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 6483f7cc7..ccad77c24 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 + '_ { + /// The expressions. + pub fn exprs(&self) -> impl DoubleEndedIterator + '_ { 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 { +impl Expr { + fn cast_with_space(node: &SyntaxNode) -> Option { match node.kind() { SyntaxKind::Space { .. } => node.cast().map(Self::Space), + _ => Self::from_untyped(node), + } + } +} + +impl AstNode for Expr { + fn from_untyped(node: &SyntaxNode) -> Option { + 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 + '_ { - self.0.children().filter_map(SyntaxNode::cast) + /// The expressions the formula consists of. + pub fn exprs(&self) -> impl DoubleEndedIterator + '_ { + 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 { - 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 { + pub fn sub(&self) -> Option { self.0 .children() .skip_while(|node| !matches!(node.kind(), SyntaxKind::Underscore)) @@ -529,7 +605,7 @@ impl Script { } /// The superscript. - pub fn sup(&self) -> Option { + pub fn sup(&self) -> Option { 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 { - 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 e04054072..900e0e678 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 98f244e18..98b6d8a8e 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, diff --git a/tests/typ/compiler/field.typ b/tests/typ/compiler/field.typ index abea87fb3..1235c84e0 100644 --- a/tests/typ/compiler/field.typ +++ b/tests/typ/compiler/field.typ @@ -27,7 +27,7 @@ {(:).invalid} --- -// Error: 2-7 cannot access field on boolean +// Error: 2-7 expected dictionary or content, found boolean {false.ok} ---