mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Merge MarkupNode
and MathNode
into Expr
This commit is contained in:
parent
a6d90c1bf1
commit
7c683db367
@ -180,7 +180,7 @@ fn items() -> LangItems {
|
|||||||
em: |styles| styles.get(text::TextNode::SIZE),
|
em: |styles| styles.get(text::TextNode::SIZE),
|
||||||
dir: |styles| styles.get(text::TextNode::DIR),
|
dir: |styles| styles.get(text::TextNode::DIR),
|
||||||
space: || text::SpaceNode.pack(),
|
space: || text::SpaceNode.pack(),
|
||||||
linebreak: |justify| text::LinebreakNode { justify }.pack(),
|
linebreak: || text::LinebreakNode { justify: false }.pack(),
|
||||||
text: |text| text::TextNode(text).pack(),
|
text: |text| text::TextNode(text).pack(),
|
||||||
text_id: NodeId::of::<text::TextNode>(),
|
text_id: NodeId::of::<text::TextNode>(),
|
||||||
text_str: |content| Some(&content.to::<text::TextNode>()?.0),
|
text_str: |content| Some(&content.to::<text::TextNode>()?.0),
|
||||||
|
@ -17,7 +17,7 @@ use crate::diag::{
|
|||||||
use crate::geom::{Abs, Angle, Em, Fr, Ratio};
|
use crate::geom::{Abs, Angle, Em, Fr, Ratio};
|
||||||
use crate::syntax::ast::AstNode;
|
use crate::syntax::ast::AstNode;
|
||||||
use crate::syntax::{ast, Source, SourceId, Span, Spanned, SyntaxKind, SyntaxNode, Unit};
|
use crate::syntax::{ast, Source, SourceId, Span, Spanned, SyntaxKind, SyntaxNode, Unit};
|
||||||
use crate::util::{format_eco, EcoString, PathExt};
|
use crate::util::{EcoString, PathExt};
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
const MAX_ITERATIONS: usize = 10_000;
|
const MAX_ITERATIONS: usize = 10_000;
|
||||||
@ -98,7 +98,7 @@ impl<'a> Vm<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Access the underlying world.
|
/// Access the underlying world.
|
||||||
pub fn world(&self) -> Tracked<dyn World> {
|
pub fn world(&self) -> Tracked<'a, dyn World> {
|
||||||
self.world
|
self.world
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,38 +203,38 @@ impl Eval for ast::Markup {
|
|||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
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(
|
fn eval_markup(
|
||||||
vm: &mut Vm,
|
vm: &mut Vm,
|
||||||
nodes: &mut impl Iterator<Item = ast::MarkupNode>,
|
exprs: &mut impl Iterator<Item = ast::Expr>,
|
||||||
) -> SourceResult<Content> {
|
) -> SourceResult<Content> {
|
||||||
let flow = vm.flow.take();
|
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() {
|
while let Some(expr) = exprs.next() {
|
||||||
match node {
|
match expr {
|
||||||
ast::MarkupNode::Expr(ast::Expr::Set(set)) => {
|
ast::Expr::Set(set) => {
|
||||||
let styles = set.eval(vm)?;
|
let styles = set.eval(vm)?;
|
||||||
if vm.flow.is_some() {
|
if vm.flow.is_some() {
|
||||||
break;
|
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)?;
|
let recipe = show.eval(vm)?;
|
||||||
if vm.flow.is_some() {
|
if vm.flow.is_some() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let tail = eval_markup(vm, nodes)?;
|
let tail = eval_markup(vm, exprs)?;
|
||||||
seq.push(tail.styled_with_recipe(vm.world, recipe)?)
|
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) => {
|
Value::Label(label) => {
|
||||||
if let Some(node) =
|
if let Some(node) =
|
||||||
seq.iter_mut().rev().find(|node| node.labellable())
|
seq.iter_mut().rev().find(|node| node.labellable())
|
||||||
@ -244,7 +244,6 @@ fn eval_markup(
|
|||||||
}
|
}
|
||||||
value => seq.push(value.display().spanned(expr.span())),
|
value => seq.push(value.display().spanned(expr.span())),
|
||||||
},
|
},
|
||||||
_ => seq.push(node.eval(vm)?),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if vm.flow.is_some() {
|
if vm.flow.is_some() {
|
||||||
@ -259,33 +258,88 @@ fn eval_markup(
|
|||||||
Ok(Content::sequence(seq))
|
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;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
Ok(match self {
|
Ok(match self.newlines() {
|
||||||
Self::Space(v) => match v.newlines() {
|
|
||||||
0..=1 => (vm.items.space)(),
|
0..=1 => (vm.items.space)(),
|
||||||
_ => (vm.items.parbreak)(),
|
_ => (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()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,7 +347,7 @@ impl Eval for ast::Linebreak {
|
|||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
Ok((vm.items.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 {
|
impl Eval for ast::Shorthand {
|
||||||
type Output = Content;
|
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 {
|
impl Eval for ast::Symbol {
|
||||||
type Output = Content;
|
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 {
|
impl Eval for ast::SmartQuote {
|
||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
@ -414,48 +494,12 @@ impl Eval for ast::Math {
|
|||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
Ok((vm.items.math)(
|
let seq = self
|
||||||
self.children()
|
.exprs()
|
||||||
.map(|node| node.eval(vm))
|
.map(|expr| expr.eval_in_math(vm))
|
||||||
.collect::<SourceResult<_>>()?,
|
.collect::<SourceResult<_>>()?;
|
||||||
self.block(),
|
let block = self.block();
|
||||||
))
|
Ok((vm.items.math)(seq, 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()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -471,11 +515,10 @@ impl Eval for ast::Script {
|
|||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
Ok((vm.items.math_script)(
|
let base = self.base().eval_in_math(vm)?;
|
||||||
self.base().eval(vm)?,
|
let sub = self.sub().map(|expr| expr.eval_in_math(vm)).transpose()?;
|
||||||
self.sub().map(|node| node.eval(vm)).transpose()?,
|
let sup = self.sup().map(|expr| expr.eval_in_math(vm)).transpose()?;
|
||||||
self.sup().map(|node| node.eval(vm)).transpose()?,
|
Ok((vm.items.math_script)(base, sub, sup))
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -483,7 +526,9 @@ impl Eval for ast::Frac {
|
|||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
Ok((vm.items.math_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 {
|
impl Eval for ast::Lit {
|
||||||
type Output = Value;
|
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 {
|
impl Eval for ast::CodeBlock {
|
||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
@ -789,7 +805,11 @@ impl Eval for ast::FieldAccess {
|
|||||||
.field(&field)
|
.field(&field)
|
||||||
.ok_or_else(|| format!("unknown field {field:?}"))
|
.ok_or_else(|| format!("unknown field {field:?}"))
|
||||||
.at(span)?,
|
.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 {
|
let closure = Closure {
|
||||||
location: vm.location,
|
location: vm.location,
|
||||||
name,
|
name,
|
||||||
@ -966,7 +986,7 @@ impl Eval for ast::ShowRule {
|
|||||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
let selector = self
|
let selector = self
|
||||||
.selector()
|
.selector()
|
||||||
.map(|selector| selector.eval(vm)?.cast::<Selector>().at(selector.span()))
|
.map(|sel| sel.eval(vm)?.cast::<Selector>().at(sel.span()))
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
let transform = self.transform();
|
let transform = self.transform();
|
||||||
@ -1094,7 +1114,6 @@ impl Eval for ast::ForLoop {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let iter = self.iter().eval(vm)?;
|
let iter = self.iter().eval(vm)?;
|
||||||
|
|
||||||
let pattern = self.pattern();
|
let pattern = self.pattern();
|
||||||
let key = pattern.key().map(ast::Ident::take);
|
let key = pattern.key().map(ast::Ident::take);
|
||||||
let value = pattern.value().take();
|
let value = pattern.value().take();
|
||||||
@ -1266,28 +1285,35 @@ impl Access for ast::Parenthesized {
|
|||||||
|
|
||||||
impl Access for ast::FieldAccess {
|
impl Access for ast::FieldAccess {
|
||||||
fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
|
fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
|
||||||
Ok(match self.target().access(vm)? {
|
let value = self.target().access(vm)?;
|
||||||
Value::Dict(dict) => dict.at_mut(self.field().take().into()),
|
let Value::Dict(dict) = value else {
|
||||||
v => bail!(
|
bail!(
|
||||||
self.target().span(),
|
self.target().span(),
|
||||||
"expected dictionary, found {}",
|
"expected dictionary, found {}",
|
||||||
v.type_name(),
|
value.type_name(),
|
||||||
),
|
);
|
||||||
})
|
};
|
||||||
|
|
||||||
|
Ok(dict.at_mut(self.field().take().into()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Access for ast::MethodCall {
|
impl Access for ast::MethodCall {
|
||||||
fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
|
fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
|
||||||
let span = self.span();
|
let span = self.span();
|
||||||
let method = self.method();
|
let method = self.method().take();
|
||||||
let args = self.args().eval(vm)?;
|
let world = vm.world();
|
||||||
if methods::is_accessor(&method) {
|
|
||||||
let value = self.target().access(vm)?;
|
if !methods::is_accessor(&method) {
|
||||||
methods::call_access(value, &method, args, span)
|
|
||||||
} else {
|
|
||||||
let _ = self.eval(vm)?;
|
let _ = self.eval(vm)?;
|
||||||
bail!(span, "cannot mutate a temporary value");
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ pub struct LangItems {
|
|||||||
/// Whitespace.
|
/// Whitespace.
|
||||||
pub space: fn() -> Content,
|
pub space: fn() -> Content,
|
||||||
/// A forced line break: `\`.
|
/// A forced line break: `\`.
|
||||||
pub linebreak: fn(justify: bool) -> Content,
|
pub linebreak: fn() -> Content,
|
||||||
/// Plain text without markup.
|
/// Plain text without markup.
|
||||||
pub text: fn(text: EcoString) -> Content,
|
pub text: fn(text: EcoString) -> Content,
|
||||||
/// The id of the text node.
|
/// The id of the text node.
|
||||||
|
@ -112,6 +112,15 @@ impl Value {
|
|||||||
_ => item!(raw)(self.repr().into(), Some("typc".into()), false),
|
_ => 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 {
|
impl Default for Value {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
//! A typed layer over the untyped syntax tree.
|
//! 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::num::NonZeroUsize;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
@ -54,26 +54,26 @@ node! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Markup {
|
impl Markup {
|
||||||
/// The children.
|
/// The expressions.
|
||||||
pub fn children(&self) -> impl DoubleEndedIterator<Item = MarkupNode> + '_ {
|
pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ {
|
||||||
let mut was_stmt = false;
|
let mut was_stmt = false;
|
||||||
self.0
|
self.0
|
||||||
.children()
|
.children()
|
||||||
.filter(move |node| {
|
.filter(move |node| {
|
||||||
// Ignore linebreak directly after statements without semicolons.
|
// Ignore newline directly after statements without semicolons.
|
||||||
let kind = node.kind();
|
let kind = node.kind();
|
||||||
let keep =
|
let keep =
|
||||||
!was_stmt || !matches!(kind, SyntaxKind::Space { newlines: 1 });
|
!was_stmt || !matches!(kind, SyntaxKind::Space { newlines: 1 });
|
||||||
was_stmt = kind.is_stmt();
|
was_stmt = kind.is_stmt();
|
||||||
keep
|
keep
|
||||||
})
|
})
|
||||||
.filter_map(SyntaxNode::cast)
|
.filter_map(Expr::cast_with_space)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A single piece of markup.
|
/// An expression in markup, math or code.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||||
pub enum MarkupNode {
|
pub enum Expr {
|
||||||
/// Whitespace.
|
/// Whitespace.
|
||||||
Space(Space),
|
Space(Space),
|
||||||
/// A forced line break: `\`.
|
/// A forced line break: `\`.
|
||||||
@ -107,14 +107,78 @@ pub enum MarkupNode {
|
|||||||
Enum(EnumItem),
|
Enum(EnumItem),
|
||||||
/// An item in a term list: `/ Term: Details`.
|
/// An item in a term list: `/ Term: Details`.
|
||||||
Term(TermItem),
|
Term(TermItem),
|
||||||
/// An expression.
|
/// A math formula: `$x$`, `$ x^2 $`.
|
||||||
Expr(Expr),
|
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 {
|
impl Expr {
|
||||||
fn from_untyped(node: &SyntaxNode) -> Option<Self> {
|
fn cast_with_space(node: &SyntaxNode) -> Option<Self> {
|
||||||
match node.kind() {
|
match node.kind() {
|
||||||
SyntaxKind::Space { .. } => node.cast().map(Self::Space),
|
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::Linebreak => node.cast().map(Self::Linebreak),
|
||||||
SyntaxKind::Text(_) => node.cast().map(Self::Text),
|
SyntaxKind::Text(_) => node.cast().map(Self::Text),
|
||||||
SyntaxKind::Escape(_) => node.cast().map(Self::Escape),
|
SyntaxKind::Escape(_) => node.cast().map(Self::Escape),
|
||||||
@ -130,7 +194,35 @@ impl AstNode for MarkupNode {
|
|||||||
SyntaxKind::ListItem => node.cast().map(Self::List),
|
SyntaxKind::ListItem => node.cast().map(Self::List),
|
||||||
SyntaxKind::EnumItem => node.cast().map(Self::Enum),
|
SyntaxKind::EnumItem => node.cast().map(Self::Enum),
|
||||||
SyntaxKind::TermItem => node.cast().map(Self::Term),
|
SyntaxKind::TermItem => node.cast().map(Self::Term),
|
||||||
_ => 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::List(v) => v.as_untyped(),
|
||||||
Self::Enum(v) => v.as_untyped(),
|
Self::Enum(v) => v.as_untyped(),
|
||||||
Self::Term(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! {
|
node! {
|
||||||
/// Whitespace.
|
/// Whitespace.
|
||||||
Space
|
Space
|
||||||
@ -418,78 +557,15 @@ node! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Math {
|
impl Math {
|
||||||
/// The children.
|
/// The expressions the formula consists of.
|
||||||
pub fn children(&self) -> impl DoubleEndedIterator<Item = MathNode> + '_ {
|
pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ {
|
||||||
self.0.children().filter_map(SyntaxNode::cast)
|
self.0.children().filter_map(Expr::cast_with_space)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the formula should be displayed as a separate block.
|
/// Whether the formula should be displayed as a separate block.
|
||||||
pub fn block(&self) -> bool {
|
pub fn block(&self) -> bool {
|
||||||
matches!(self.children().next(), Some(MathNode::Space(_)))
|
matches!(self.exprs().next(), Some(Expr::Space(_)))
|
||||||
&& matches!(self.children().last(), Some(MathNode::Space(_)))
|
&& matches!(self.exprs().last(), Some(Expr::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(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -515,12 +591,12 @@ node! {
|
|||||||
|
|
||||||
impl Script {
|
impl Script {
|
||||||
/// The base of the 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")
|
self.0.cast_first_child().expect("script node is missing base")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The subscript.
|
/// The subscript.
|
||||||
pub fn sub(&self) -> Option<MathNode> {
|
pub fn sub(&self) -> Option<Expr> {
|
||||||
self.0
|
self.0
|
||||||
.children()
|
.children()
|
||||||
.skip_while(|node| !matches!(node.kind(), SyntaxKind::Underscore))
|
.skip_while(|node| !matches!(node.kind(), SyntaxKind::Underscore))
|
||||||
@ -529,7 +605,7 @@ impl Script {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The superscript.
|
/// The superscript.
|
||||||
pub fn sup(&self) -> Option<MathNode> {
|
pub fn sup(&self) -> Option<Expr> {
|
||||||
self.0
|
self.0
|
||||||
.children()
|
.children()
|
||||||
.skip_while(|node| !matches!(node.kind(), SyntaxKind::Hat))
|
.skip_while(|node| !matches!(node.kind(), SyntaxKind::Hat))
|
||||||
@ -545,12 +621,12 @@ node! {
|
|||||||
|
|
||||||
impl Frac {
|
impl Frac {
|
||||||
/// The numerator.
|
/// The numerator.
|
||||||
pub fn num(&self) -> MathNode {
|
pub fn num(&self) -> Expr {
|
||||||
self.0.cast_first_child().expect("fraction is missing numerator")
|
self.0.cast_first_child().expect("fraction is missing numerator")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The denominator.
|
/// The denominator.
|
||||||
pub fn denom(&self) -> MathNode {
|
pub fn denom(&self) -> Expr {
|
||||||
self.0.cast_last_child().expect("fraction is missing denominator")
|
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! {
|
node! {
|
||||||
/// A literal: `1`, `true`, ...
|
/// A literal: `1`, `true`, ...
|
||||||
Lit: SyntaxKind::None
|
Lit: SyntaxKind::None
|
||||||
|
@ -586,11 +586,23 @@ fn expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) -> ParseResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn primary(p: &mut Parser, atomic: bool) -> ParseResult {
|
fn primary(p: &mut Parser, atomic: bool) -> ParseResult {
|
||||||
if literal(p) {
|
match p.peek() {
|
||||||
return Ok(());
|
// 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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
match p.peek() {
|
|
||||||
// Things that start with an identifier.
|
// Things that start with an identifier.
|
||||||
Some(SyntaxKind::Ident(_)) => {
|
Some(SyntaxKind::Ident(_)) => {
|
||||||
let marker = p.marker();
|
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 {
|
fn ident(p: &mut Parser) -> ParseResult {
|
||||||
match p.peek() {
|
match p.peek() {
|
||||||
Some(SyntaxKind::Ident(_)) => {
|
Some(SyntaxKind::Ident(_)) => {
|
||||||
|
@ -523,8 +523,9 @@ impl Tokens<'_> {
|
|||||||
// Math.
|
// Math.
|
||||||
'$' => SyntaxKind::Dollar,
|
'$' => SyntaxKind::Dollar,
|
||||||
|
|
||||||
// Labels.
|
// Labels and raw.
|
||||||
'<' if self.s.at(is_id_continue) => self.label(),
|
'<' if self.s.at(is_id_continue) => self.label(),
|
||||||
|
'`' => self.raw(),
|
||||||
|
|
||||||
// Two-char operators.
|
// Two-char operators.
|
||||||
'=' if self.s.eat_if('=') => SyntaxKind::EqEq,
|
'=' if self.s.eat_if('=') => SyntaxKind::EqEq,
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
{(:).invalid}
|
{(:).invalid}
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 2-7 cannot access field on boolean
|
// Error: 2-7 expected dictionary or content, found boolean
|
||||||
{false.ok}
|
{false.ok}
|
||||||
|
|
||||||
---
|
---
|
||||||
|
Loading…
x
Reference in New Issue
Block a user