Merge MarkupNode and MathNode into Expr

This commit is contained in:
Laurenz 2022-12-31 09:37:55 +01:00
parent a6d90c1bf1
commit 7c683db367
8 changed files with 363 additions and 394 deletions

View File

@ -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::TextNode>(),
text_str: |content| Some(&content.to::<text::TextNode>()?.0),

View File

@ -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,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<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::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 {
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<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)
}
}

View File

@ -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.

View File

@ -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 {

View File

@ -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

View File

@ -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(_)) => {

View File

@ -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,

View File

@ -27,7 +27,7 @@
{(:).invalid}
---
// Error: 2-7 cannot access field on boolean
// Error: 2-7 expected dictionary or content, found boolean
{false.ok}
---