mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Merge typst
and typst-library
This commit is contained in:
parent
76e173b78b
commit
7eebafa783
61
Cargo.lock
generated
61
Cargo.lock
generated
@ -2704,14 +2704,26 @@ checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
|
||||
name = "typst"
|
||||
version = "0.9.0"
|
||||
dependencies = [
|
||||
"az",
|
||||
"bitflags 2.4.1",
|
||||
"chinese-number",
|
||||
"ciborium",
|
||||
"comemo",
|
||||
"csv",
|
||||
"ecow",
|
||||
"fontdb",
|
||||
"hayagriva",
|
||||
"hypher",
|
||||
"icu_properties",
|
||||
"icu_provider",
|
||||
"icu_provider_adapters",
|
||||
"icu_provider_blob",
|
||||
"icu_segmenter",
|
||||
"image",
|
||||
"indexmap 2.0.2",
|
||||
"kurbo",
|
||||
"lasso",
|
||||
"lipsum",
|
||||
"log",
|
||||
"once_cell",
|
||||
"palette",
|
||||
@ -2720,16 +2732,22 @@ dependencies = [
|
||||
"roxmltree",
|
||||
"rustybuzz",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml 0.9.27",
|
||||
"siphasher",
|
||||
"smallvec",
|
||||
"stacker",
|
||||
"syntect",
|
||||
"time",
|
||||
"toml",
|
||||
"tracing",
|
||||
"ttf-parser",
|
||||
"typed-arena",
|
||||
"typst-macros",
|
||||
"typst-syntax",
|
||||
"unicode-bidi",
|
||||
"unicode-math-class",
|
||||
"unicode-script",
|
||||
"unicode-segmentation",
|
||||
"usvg",
|
||||
"wasmi",
|
||||
@ -2771,7 +2789,6 @@ dependencies = [
|
||||
"tracing-flame",
|
||||
"tracing-subscriber",
|
||||
"typst",
|
||||
"typst-library",
|
||||
"typst-pdf",
|
||||
"typst-render",
|
||||
"typst-svg",
|
||||
@ -2795,7 +2812,6 @@ dependencies = [
|
||||
"syntect",
|
||||
"typed-arena",
|
||||
"typst",
|
||||
"typst-library",
|
||||
"unicode_names2",
|
||||
"unscanny",
|
||||
"yaml-front-matter",
|
||||
@ -2814,46 +2830,6 @@ dependencies = [
|
||||
"unscanny",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typst-library"
|
||||
version = "0.9.0"
|
||||
dependencies = [
|
||||
"az",
|
||||
"chinese-number",
|
||||
"ciborium",
|
||||
"comemo",
|
||||
"csv",
|
||||
"ecow",
|
||||
"hayagriva",
|
||||
"hypher",
|
||||
"icu_properties",
|
||||
"icu_provider",
|
||||
"icu_provider_adapters",
|
||||
"icu_provider_blob",
|
||||
"icu_segmenter",
|
||||
"indexmap 2.0.2",
|
||||
"kurbo",
|
||||
"lipsum",
|
||||
"log",
|
||||
"once_cell",
|
||||
"roxmltree",
|
||||
"rustybuzz",
|
||||
"serde_json",
|
||||
"serde_yaml 0.9.27",
|
||||
"smallvec",
|
||||
"syntect",
|
||||
"time",
|
||||
"toml",
|
||||
"tracing",
|
||||
"ttf-parser",
|
||||
"typed-arena",
|
||||
"typst",
|
||||
"unicode-bidi",
|
||||
"unicode-math-class",
|
||||
"unicode-script",
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typst-macros"
|
||||
version = "0.9.0"
|
||||
@ -2948,7 +2924,6 @@ dependencies = [
|
||||
"tiny-skia",
|
||||
"ttf-parser",
|
||||
"typst",
|
||||
"typst-library",
|
||||
"typst-pdf",
|
||||
"typst-render",
|
||||
"typst-svg",
|
||||
|
@ -20,7 +20,6 @@ typst = { path = "crates/typst" }
|
||||
typst-cli = { path = "crates/typst-cli" }
|
||||
typst-docs = { path = "crates/typst-docs" }
|
||||
typst-ide = { path = "crates/typst-ide" }
|
||||
typst-library = { path = "crates/typst-library" }
|
||||
typst-macros = { path = "crates/typst-macros" }
|
||||
typst-pdf = { path = "crates/typst-pdf" }
|
||||
typst-render = { path = "crates/typst-render" }
|
||||
@ -98,7 +97,7 @@ tar = "0.4"
|
||||
tempfile = "3.7.0"
|
||||
time = { version = "0.3.20", features = ["formatting", "macros", "parsing"] }
|
||||
tiny-skia = "0.11"
|
||||
toml = { version = "0.8", default-features = false, features = ["parse"] }
|
||||
toml = { version = "0.8", default-features = false, features = ["parse", "display"] }
|
||||
tracing = "0.1.37"
|
||||
tracing-error = "0.2"
|
||||
tracing-flame = "0.2.0"
|
||||
|
@ -21,7 +21,6 @@ doc = false
|
||||
|
||||
[dependencies]
|
||||
typst = { workspace = true }
|
||||
typst-library = { workspace = true }
|
||||
typst-pdf = { workspace = true }
|
||||
typst-render = { workspace = true }
|
||||
typst-svg = { workspace = true }
|
||||
|
@ -1,9 +1,8 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use semver::Version;
|
||||
|
||||
use clap::{ArgAction, Args, Parser, Subcommand, ValueEnum};
|
||||
use semver::Version;
|
||||
|
||||
/// The character typically used to separate path components
|
||||
/// in environment variables.
|
||||
|
@ -4,12 +4,14 @@ use std::path::{Path, PathBuf};
|
||||
use chrono::{Datelike, Timelike};
|
||||
use codespan_reporting::diagnostic::{Diagnostic, Label};
|
||||
use codespan_reporting::term::{self, termcolor};
|
||||
use ecow::eco_format;
|
||||
use termcolor::{ColorChoice, StandardStream};
|
||||
use typst::diag::{bail, Severity, SourceDiagnostic, StrResult};
|
||||
use typst::doc::Document;
|
||||
use typst::eval::{eco_format, Datetime, Tracer};
|
||||
use typst::geom::Color;
|
||||
use typst::eval::Tracer;
|
||||
use typst::foundations::Datetime;
|
||||
use typst::model::Document;
|
||||
use typst::syntax::{FileId, Source, Span};
|
||||
use typst::visualize::Color;
|
||||
use typst::{World, WorldExt};
|
||||
|
||||
use crate::args::{CompileCommand, DiagnosticFormat, OutputFormat};
|
||||
|
@ -4,7 +4,7 @@ use std::path::PathBuf;
|
||||
|
||||
use fontdb::{Database, Source};
|
||||
use typst::diag::StrResult;
|
||||
use typst::font::{Font, FontBook, FontInfo, FontVariant};
|
||||
use typst::text::{Font, FontBook, FontInfo, FontVariant};
|
||||
|
||||
use crate::args::FontsCommand;
|
||||
|
||||
@ -106,7 +106,7 @@ impl FontSearcher {
|
||||
#[cfg(feature = "embed-fonts")]
|
||||
fn add_embedded(&mut self) {
|
||||
let mut process = |bytes: &'static [u8]| {
|
||||
let buffer = typst::eval::Bytes::from_static(bytes);
|
||||
let buffer = typst::foundations::Bytes::from_static(bytes);
|
||||
for (i, font) in Font::iter(buffer).enumerate() {
|
||||
self.book.push(font.info().clone());
|
||||
self.fonts.push(FontSlot {
|
||||
|
@ -1,10 +1,13 @@
|
||||
use comemo::Track;
|
||||
use ecow::{eco_format, EcoString};
|
||||
use serde::Serialize;
|
||||
use typst::diag::{bail, StrResult};
|
||||
use typst::eval::{eval_string, EvalMode, Tracer};
|
||||
use typst::model::Introspector;
|
||||
use typst::foundations::{Content, IntoValue, LocatableSelector, Scope};
|
||||
use typst::introspection::Introspector;
|
||||
use typst::model::Document;
|
||||
use typst::syntax::Span;
|
||||
use typst::World;
|
||||
use typst_library::prelude::*;
|
||||
|
||||
use crate::args::{QueryCommand, SerializationFormat};
|
||||
use crate::compile::print_diagnostics;
|
||||
@ -95,7 +98,7 @@ fn format(elements: Vec<Content>, command: &QueryCommand) -> StrResult<String> {
|
||||
.collect();
|
||||
|
||||
if command.one {
|
||||
let Some(value) = mapped.get(0) else {
|
||||
let Some(value) = mapped.first() else {
|
||||
bail!("no such field found for element");
|
||||
};
|
||||
serialize(value, command.format)
|
||||
|
@ -6,8 +6,9 @@ use inferno::flamegraph::Options;
|
||||
use tracing::metadata::LevelFilter;
|
||||
use tracing_error::ErrorLayer;
|
||||
use tracing_flame::{FlameLayer, FlushGuard};
|
||||
use tracing_subscriber::fmt;
|
||||
use tracing_subscriber::prelude::*;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use tracing_subscriber::{fmt, Layer};
|
||||
|
||||
use crate::args::{CliArguments, Command};
|
||||
|
||||
|
@ -1,13 +1,12 @@
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::io::{Cursor, Read, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::{env, fs};
|
||||
|
||||
use ecow::eco_format;
|
||||
use semver::Version;
|
||||
use serde::Deserialize;
|
||||
use tempfile::NamedTempFile;
|
||||
use typst::diag::{bail, StrResult};
|
||||
use typst::eval::eco_format;
|
||||
use xz2::bufread::XzDecoder;
|
||||
use zip::ZipArchive;
|
||||
|
||||
|
@ -3,11 +3,11 @@ use std::io::{self, IsTerminal, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use codespan_reporting::term::{self, termcolor};
|
||||
use ecow::eco_format;
|
||||
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
|
||||
use same_file::is_same_file;
|
||||
use termcolor::WriteColor;
|
||||
use typst::diag::StrResult;
|
||||
use typst::eval::eco_format;
|
||||
|
||||
use crate::args::CompileCommand;
|
||||
use crate::color_stream;
|
||||
|
@ -5,13 +5,14 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use chrono::{DateTime, Datelike, Local};
|
||||
use comemo::Prehashed;
|
||||
use ecow::eco_format;
|
||||
use typst::diag::{FileError, FileResult, StrResult};
|
||||
use typst::doc::Frame;
|
||||
use typst::eval::{eco_format, Bytes, Datetime, Library};
|
||||
use typst::font::{Font, FontBook};
|
||||
use typst::foundations::{Bytes, Datetime};
|
||||
use typst::layout::Frame;
|
||||
use typst::syntax::{FileId, Source, VirtualPath};
|
||||
use typst::text::{Font, FontBook};
|
||||
use typst::util::hash128;
|
||||
use typst::World;
|
||||
use typst::{Library, World};
|
||||
|
||||
use crate::args::SharedArgs;
|
||||
use crate::fonts::{FontSearcher, FontSlot};
|
||||
@ -75,7 +76,7 @@ impl SystemWorld {
|
||||
input,
|
||||
root,
|
||||
main: FileId::new(None, main_path),
|
||||
library: Prehashed::new(typst_library::build()),
|
||||
library: Prehashed::new(Library::build()),
|
||||
book: Prehashed::new(searcher.book),
|
||||
fonts: searcher.fonts,
|
||||
slots: RefCell::default(),
|
||||
|
@ -12,7 +12,6 @@ bench = false
|
||||
|
||||
[dependencies]
|
||||
typst = { workspace = true }
|
||||
typst-library = { workspace = true }
|
||||
comemo = { workspace = true }
|
||||
ecow = { workspace = true }
|
||||
heck = { workspace = true }
|
||||
|
@ -4,7 +4,7 @@ use std::fmt::Write;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{Html, Resolver};
|
||||
use crate::{Html, Resolver};
|
||||
|
||||
/// Build HTML detailing the contributors between two tags.
|
||||
pub fn contributors(resolver: &dyn Resolver, from: &str, to: &str) -> Option<Html> {
|
||||
|
@ -8,11 +8,12 @@ use pulldown_cmark as md;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typed_arena::Arena;
|
||||
use typst::diag::{FileResult, StrResult};
|
||||
use typst::eval::{Bytes, Datetime, Library, Tracer};
|
||||
use typst::font::{Font, FontBook};
|
||||
use typst::geom::{Abs, Point, Size};
|
||||
use typst::eval::Tracer;
|
||||
use typst::foundations::{Bytes, Datetime};
|
||||
use typst::layout::{Abs, Point, Size};
|
||||
use typst::syntax::{FileId, Source, VirtualPath};
|
||||
use typst::World;
|
||||
use typst::text::{Font, FontBook};
|
||||
use typst::{Library, World};
|
||||
use unscanny::Scanner;
|
||||
use yaml_front_matter::YamlFrontMatter;
|
||||
|
||||
@ -360,13 +361,13 @@ fn code_block(resolver: &dyn Resolver, lang: &str, text: &str) -> Html {
|
||||
buf.push_str("</pre>");
|
||||
return Html::new(buf);
|
||||
} else if !matches!(lang, "example" | "typ" | "preview") {
|
||||
let set = &*typst_library::text::SYNTAXES;
|
||||
let set = &*typst::text::RAW_SYNTAXES;
|
||||
let buf = syntect::html::highlighted_html_for_string(
|
||||
&display,
|
||||
set,
|
||||
set.find_syntax_by_token(lang)
|
||||
.unwrap_or_else(|| panic!("unsupported highlighting language: {lang}")),
|
||||
&typst_library::text::THEME,
|
||||
&typst::text::RAW_THEME,
|
||||
)
|
||||
.expect("failed to highlight code");
|
||||
return Html::new(buf);
|
||||
|
@ -5,9 +5,9 @@ mod html;
|
||||
mod link;
|
||||
mod model;
|
||||
|
||||
pub use contribs::{contributors, Author, Commit};
|
||||
pub use html::Html;
|
||||
pub use model::*;
|
||||
pub use self::contribs::*;
|
||||
pub use self::html::*;
|
||||
pub use self::model::*;
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
@ -20,30 +20,48 @@ use serde::de::DeserializeOwned;
|
||||
use serde::Deserialize;
|
||||
use serde_yaml as yaml;
|
||||
use typst::diag::{bail, StrResult};
|
||||
use typst::doc::Frame;
|
||||
use typst::eval::{
|
||||
CastInfo, Func, Library, Module, ParamInfo, Repr, Scope, Smart, Type, Value,
|
||||
use typst::foundations::{
|
||||
CastInfo, Category, Func, Module, ParamInfo, Repr, Scope, Smart, Type, Value,
|
||||
FOUNDATIONS,
|
||||
};
|
||||
use typst::font::{Font, FontBook};
|
||||
use typst::geom::Abs;
|
||||
use typst_library::layout::{Margin, PageElem};
|
||||
use typst::introspection::INTROSPECTION;
|
||||
use typst::layout::{Abs, Frame, Margin, PageElem, LAYOUT};
|
||||
use typst::loading::DATA_LOADING;
|
||||
use typst::math::MATH;
|
||||
use typst::model::MODEL;
|
||||
use typst::symbols::SYMBOLS;
|
||||
use typst::text::{Font, FontBook, TEXT};
|
||||
use typst::visualize::VISUALIZE;
|
||||
use typst::Library;
|
||||
|
||||
static DOCS_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../../docs");
|
||||
static FILE_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../../assets/files");
|
||||
static FONT_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../../assets/fonts");
|
||||
|
||||
static CATEGORIES: Lazy<yaml::Mapping> = Lazy::new(|| yaml("reference/categories.yml"));
|
||||
static GROUPS: Lazy<Vec<GroupData>> = Lazy::new(|| yaml("reference/groups.yml"));
|
||||
static GROUPS: Lazy<Vec<GroupData>> = Lazy::new(|| {
|
||||
let mut groups: Vec<GroupData> = yaml("reference/groups.yml");
|
||||
for group in &mut groups {
|
||||
if group.filter.is_empty() {
|
||||
group.filter = group
|
||||
.module()
|
||||
.scope()
|
||||
.iter()
|
||||
.filter(|(_, v)| matches!(v, Value::Func(_)))
|
||||
.map(|(k, _)| k.clone())
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
groups
|
||||
});
|
||||
|
||||
static LIBRARY: Lazy<Prehashed<Library>> = Lazy::new(|| {
|
||||
let mut lib = typst_library::build();
|
||||
let mut lib = Library::build();
|
||||
lib.styles
|
||||
.set(PageElem::set_width(Smart::Custom(Abs::pt(240.0).into())));
|
||||
lib.styles.set(PageElem::set_height(Smart::Auto));
|
||||
lib.styles.set(PageElem::set_margin(Margin::splat(Some(Smart::Custom(
|
||||
Abs::pt(15.0).into(),
|
||||
)))));
|
||||
typst::eval::set_lang_items(lib.items.clone());
|
||||
Prehashed::new(lib)
|
||||
});
|
||||
|
||||
@ -128,14 +146,15 @@ fn reference_pages(resolver: &dyn Resolver) -> PageModel {
|
||||
.with_part("Language"),
|
||||
markdown_page(resolver, "/docs/reference/", "reference/styling.md"),
|
||||
markdown_page(resolver, "/docs/reference/", "reference/scripting.md"),
|
||||
category_page(resolver, "foundations").with_part("Library"),
|
||||
category_page(resolver, "text"),
|
||||
category_page(resolver, "math"),
|
||||
category_page(resolver, "layout"),
|
||||
category_page(resolver, "visualize"),
|
||||
category_page(resolver, "meta"),
|
||||
category_page(resolver, "symbols"),
|
||||
category_page(resolver, "data-loading"),
|
||||
category_page(resolver, FOUNDATIONS).with_part("Library"),
|
||||
category_page(resolver, MODEL),
|
||||
category_page(resolver, TEXT),
|
||||
category_page(resolver, MATH),
|
||||
category_page(resolver, SYMBOLS),
|
||||
category_page(resolver, LAYOUT),
|
||||
category_page(resolver, VISUALIZE),
|
||||
category_page(resolver, INTROSPECTION),
|
||||
category_page(resolver, DATA_LOADING),
|
||||
];
|
||||
page
|
||||
}
|
||||
@ -152,50 +171,73 @@ fn guide_pages(resolver: &dyn Resolver) -> PageModel {
|
||||
|
||||
/// Build the packages section.
|
||||
fn packages_page(resolver: &dyn Resolver) -> PageModel {
|
||||
let md = DOCS_DIR
|
||||
.get_file("reference/packages.md")
|
||||
.unwrap()
|
||||
.contents_utf8()
|
||||
.unwrap();
|
||||
PageModel {
|
||||
route: "/docs/packages/".into(),
|
||||
title: "Packages".into(),
|
||||
description: "Packages for Typst.".into(),
|
||||
part: None,
|
||||
outline: vec![],
|
||||
body: BodyModel::Packages(Html::markdown(
|
||||
resolver,
|
||||
category_details("packages"),
|
||||
Some(1),
|
||||
)),
|
||||
body: BodyModel::Packages(Html::markdown(resolver, md, Some(1))),
|
||||
children: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a page for a category.
|
||||
#[track_caller]
|
||||
fn category_page(resolver: &dyn Resolver, category: &str) -> PageModel {
|
||||
let route = eco_format!("/docs/reference/{category}/");
|
||||
fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel {
|
||||
let route = eco_format!("/docs/reference/{}/", category.name());
|
||||
let mut children = vec![];
|
||||
let mut items = vec![];
|
||||
let mut shorthands = None;
|
||||
let mut markup = vec![];
|
||||
let mut math = vec![];
|
||||
|
||||
let (module, path): (&Module, &[&str]) = match category {
|
||||
"math" => (&LIBRARY.math, &["math"]),
|
||||
_ => (&LIBRARY.global, &[]),
|
||||
let (module, path): (&Module, &[&str]) = if category == MATH {
|
||||
(&LIBRARY.math, &["math"])
|
||||
} else {
|
||||
(&LIBRARY.global, &[])
|
||||
};
|
||||
|
||||
// Add groups.
|
||||
for mut group in GROUPS.iter().filter(|g| g.category == category).cloned() {
|
||||
let mut focus = module;
|
||||
if matches!(group.name.as_str(), "calc" | "sys") {
|
||||
focus = get_module(focus, &group.name).unwrap();
|
||||
group.functions = focus
|
||||
.scope()
|
||||
.iter()
|
||||
.filter(|(_, v)| matches!(v, Value::Func(_)))
|
||||
.map(|(k, _)| k.clone())
|
||||
.collect();
|
||||
for group in GROUPS.iter().filter(|g| g.category == category.name()).cloned() {
|
||||
if matches!(group.name.as_str(), "sym" | "emoji") {
|
||||
let subpage = symbols_page(resolver, &route, &group);
|
||||
let BodyModel::Symbols(model) = &subpage.body else { continue };
|
||||
let list = &model.list;
|
||||
markup.extend(
|
||||
list.iter()
|
||||
.filter(|symbol| symbol.markup_shorthand.is_some())
|
||||
.cloned(),
|
||||
);
|
||||
math.extend(
|
||||
list.iter().filter(|symbol| symbol.math_shorthand.is_some()).cloned(),
|
||||
);
|
||||
|
||||
items.push(CategoryItem {
|
||||
name: group.name.clone(),
|
||||
route: subpage.route.clone(),
|
||||
oneliner: oneliner(category.docs()).into(),
|
||||
code: true,
|
||||
});
|
||||
children.push(subpage);
|
||||
continue;
|
||||
}
|
||||
let (child, item) = group_page(resolver, &route, &group, focus.scope());
|
||||
|
||||
let (child, item) = group_page(resolver, &route, &group);
|
||||
children.push(child);
|
||||
items.push(item);
|
||||
}
|
||||
|
||||
// Add symbol pages. These are ordered manually.
|
||||
if category == SYMBOLS {
|
||||
shorthands = Some(ShorthandsModel { markup, math });
|
||||
}
|
||||
|
||||
// Add functions.
|
||||
let scope = module.scope();
|
||||
for (name, value) in scope.iter() {
|
||||
@ -203,9 +245,9 @@ fn category_page(resolver: &dyn Resolver, category: &str) -> PageModel {
|
||||
continue;
|
||||
}
|
||||
|
||||
if category == "math" {
|
||||
if category == MATH {
|
||||
// Skip grouped functions.
|
||||
if GROUPS.iter().flat_map(|group| &group.functions).any(|f| f == name) {
|
||||
if GROUPS.iter().flat_map(|group| &group.filter).any(|f| f == name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -242,54 +284,35 @@ fn category_page(resolver: &dyn Resolver, category: &str) -> PageModel {
|
||||
}
|
||||
}
|
||||
|
||||
children.sort_by_cached_key(|child| child.title.clone());
|
||||
items.sort_by_cached_key(|item| item.name.clone());
|
||||
|
||||
// Add symbol pages. These are ordered manually.
|
||||
let mut shorthands = None;
|
||||
if category == "symbols" {
|
||||
let mut markup = vec![];
|
||||
let mut math = vec![];
|
||||
for module in ["sym", "emoji"] {
|
||||
let subpage = symbols_page(resolver, &route, module);
|
||||
let BodyModel::Symbols(model) = &subpage.body else { continue };
|
||||
let list = &model.list;
|
||||
markup.extend(
|
||||
list.iter()
|
||||
.filter(|symbol| symbol.markup_shorthand.is_some())
|
||||
.cloned(),
|
||||
);
|
||||
math.extend(
|
||||
list.iter().filter(|symbol| symbol.math_shorthand.is_some()).cloned(),
|
||||
);
|
||||
|
||||
items.push(CategoryItem {
|
||||
name: module.into(),
|
||||
route: subpage.route.clone(),
|
||||
oneliner: oneliner(category_details(module)).into(),
|
||||
code: true,
|
||||
});
|
||||
children.push(subpage);
|
||||
}
|
||||
shorthands = Some(ShorthandsModel { markup, math });
|
||||
if category != SYMBOLS {
|
||||
children.sort_by_cached_key(|child| child.title.clone());
|
||||
items.sort_by_cached_key(|item| item.name.clone());
|
||||
}
|
||||
|
||||
let name: EcoString = category.to_title_case().into();
|
||||
|
||||
let details = Html::markdown(resolver, category_details(category), Some(1));
|
||||
let name = category.title();
|
||||
let details = Html::markdown(resolver, category.docs(), Some(1));
|
||||
let mut outline = vec![OutlineItem::from_name("Summary")];
|
||||
outline.extend(details.outline());
|
||||
outline.push(OutlineItem::from_name("Definitions"));
|
||||
if shorthands.is_some() {
|
||||
outline.push(OutlineItem::from_name("Shorthands"));
|
||||
}
|
||||
|
||||
PageModel {
|
||||
route,
|
||||
title: name.clone(),
|
||||
title: name.into(),
|
||||
description: eco_format!(
|
||||
"Documentation for functions related to {name} in Typst."
|
||||
),
|
||||
part: None,
|
||||
outline,
|
||||
body: BodyModel::Category(CategoryModel { name, details, items, shorthands }),
|
||||
body: BodyModel::Category(CategoryModel {
|
||||
name: category.name(),
|
||||
title: category.title(),
|
||||
details,
|
||||
items,
|
||||
shorthands,
|
||||
}),
|
||||
children,
|
||||
}
|
||||
}
|
||||
@ -498,18 +521,17 @@ fn group_page(
|
||||
resolver: &dyn Resolver,
|
||||
parent: &str,
|
||||
group: &GroupData,
|
||||
scope: &Scope,
|
||||
) -> (PageModel, CategoryItem) {
|
||||
let mut functions = vec![];
|
||||
let mut outline = vec![OutlineItem::from_name("Summary")];
|
||||
|
||||
let path: Vec<_> = group.path.iter().map(|s| s.as_str()).collect();
|
||||
let details = Html::markdown(resolver, &group.description, Some(1));
|
||||
let details = Html::markdown(resolver, &group.details, Some(1));
|
||||
outline.extend(details.outline());
|
||||
|
||||
let mut outline_items = vec![];
|
||||
for name in &group.functions {
|
||||
let value = scope.get(name).unwrap();
|
||||
for name in &group.filter {
|
||||
let value = group.module().scope().get(name).unwrap();
|
||||
let Value::Func(func) = value else { panic!("not a function") };
|
||||
let func = func_model(resolver, func, &path, true);
|
||||
let id_base = urlify(&eco_format!("functions-{}", func.name));
|
||||
@ -530,13 +552,13 @@ fn group_page(
|
||||
|
||||
let model = PageModel {
|
||||
route: eco_format!("{parent}{}", group.name),
|
||||
title: group.display.clone(),
|
||||
title: group.title.clone(),
|
||||
description: eco_format!("Documentation for the {} functions.", group.name),
|
||||
part: None,
|
||||
outline,
|
||||
body: BodyModel::Group(GroupModel {
|
||||
name: group.name.clone(),
|
||||
title: group.display.clone(),
|
||||
title: group.title.clone(),
|
||||
details,
|
||||
functions,
|
||||
}),
|
||||
@ -546,7 +568,7 @@ fn group_page(
|
||||
let item = CategoryItem {
|
||||
name: group.name.clone(),
|
||||
route: model.route.clone(),
|
||||
oneliner: oneliner(&group.description).into(),
|
||||
oneliner: oneliner(&group.details).into(),
|
||||
code: false,
|
||||
};
|
||||
|
||||
@ -601,19 +623,12 @@ fn type_outline(model: &TypeModel) -> Vec<OutlineItem> {
|
||||
}
|
||||
|
||||
/// Create a page for symbols.
|
||||
fn symbols_page(resolver: &dyn Resolver, parent: &str, name: &str) -> PageModel {
|
||||
let module = get_module(&LIBRARY.global, name).unwrap();
|
||||
let title = match name {
|
||||
"sym" => "General",
|
||||
"emoji" => "Emoji",
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let model = symbols_model(resolver, name, title, module.scope());
|
||||
fn symbols_page(resolver: &dyn Resolver, parent: &str, group: &GroupData) -> PageModel {
|
||||
let model = symbols_model(resolver, group);
|
||||
PageModel {
|
||||
route: eco_format!("{parent}{name}/"),
|
||||
title: title.into(),
|
||||
description: eco_format!("Documentation for the `{name}` module."),
|
||||
route: eco_format!("{parent}{}/", group.name),
|
||||
title: group.title.clone(),
|
||||
description: eco_format!("Documentation for the `{}` module.", group.name),
|
||||
part: None,
|
||||
outline: vec![],
|
||||
body: BodyModel::Symbols(model),
|
||||
@ -622,14 +637,9 @@ fn symbols_page(resolver: &dyn Resolver, parent: &str, name: &str) -> PageModel
|
||||
}
|
||||
|
||||
/// Produce a symbol list's model.
|
||||
fn symbols_model(
|
||||
resolver: &dyn Resolver,
|
||||
name: &str,
|
||||
title: &'static str,
|
||||
scope: &Scope,
|
||||
) -> SymbolsModel {
|
||||
fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel {
|
||||
let mut list = vec![];
|
||||
for (name, value) in scope.iter() {
|
||||
for (name, value) in group.module().scope().iter() {
|
||||
let Value::Symbol(symbol) = value else { continue };
|
||||
let complete = |variant: &str| {
|
||||
if variant.is_empty() {
|
||||
@ -649,7 +659,7 @@ fn symbols_model(
|
||||
markup_shorthand: shorthand(typst::syntax::ast::Shorthand::MARKUP_LIST),
|
||||
math_shorthand: shorthand(typst::syntax::ast::Shorthand::MATH_LIST),
|
||||
codepoint: c as u32,
|
||||
accent: typst::eval::Symbol::combining_accent(c).is_some(),
|
||||
accent: typst::symbols::Symbol::combining_accent(c).is_some(),
|
||||
unicode_name: unicode_names2::name(c)
|
||||
.map(|s| s.to_string().to_title_case().into()),
|
||||
alternates: symbol
|
||||
@ -662,8 +672,9 @@ fn symbols_model(
|
||||
}
|
||||
|
||||
SymbolsModel {
|
||||
name: title,
|
||||
details: Html::markdown(resolver, category_details(name), Some(1)),
|
||||
name: group.name.clone(),
|
||||
title: group.title.clone(),
|
||||
details: Html::markdown(resolver, &group.details, Some(1)),
|
||||
list,
|
||||
}
|
||||
}
|
||||
@ -684,15 +695,6 @@ fn yaml<T: DeserializeOwned>(path: &str) -> T {
|
||||
yaml::from_slice(file.contents()).unwrap()
|
||||
}
|
||||
|
||||
/// Load details for an identifying key.
|
||||
#[track_caller]
|
||||
fn category_details(key: &str) -> &str {
|
||||
CATEGORIES
|
||||
.get(&yaml::Value::String(key.into()))
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_else(|| panic!("missing details for {key}"))
|
||||
}
|
||||
|
||||
/// Turn a title into an URL fragment.
|
||||
pub fn urlify(title: &str) -> EcoString {
|
||||
title
|
||||
@ -752,13 +754,23 @@ const TYPE_ORDER: &[&str] = &[
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct GroupData {
|
||||
name: EcoString,
|
||||
title: EcoString,
|
||||
category: EcoString,
|
||||
display: EcoString,
|
||||
#[serde(default)]
|
||||
path: Vec<EcoString>,
|
||||
#[serde(default)]
|
||||
functions: Vec<EcoString>,
|
||||
description: EcoString,
|
||||
filter: Vec<EcoString>,
|
||||
details: EcoString,
|
||||
}
|
||||
|
||||
impl GroupData {
|
||||
fn module(&self) -> &'static Module {
|
||||
let mut focus = &LIBRARY.global;
|
||||
for path in &self.path {
|
||||
focus = get_module(focus, path).unwrap();
|
||||
}
|
||||
focus
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1,5 +1,5 @@
|
||||
use typst::diag::{bail, StrResult};
|
||||
use typst::eval::Func;
|
||||
use typst::foundations::Func;
|
||||
|
||||
use crate::{get_module, GROUPS, LIBRARY};
|
||||
|
||||
@ -55,6 +55,15 @@ fn resolve_known(head: &str) -> Option<&'static str> {
|
||||
fn resolve_definition(head: &str) -> StrResult<String> {
|
||||
let mut parts = head.trim_start_matches('$').split('.').peekable();
|
||||
let mut focus = &LIBRARY.global;
|
||||
|
||||
let Some(name) = parts.peek() else {
|
||||
bail!("missing first link component");
|
||||
};
|
||||
|
||||
let Some(category) = focus.scope().get_category(name) else {
|
||||
bail!("{name} has no category");
|
||||
};
|
||||
|
||||
while let Some(m) = parts.peek().and_then(|&name| get_module(focus, name).ok()) {
|
||||
focus = m;
|
||||
parts.next();
|
||||
@ -62,18 +71,15 @@ fn resolve_definition(head: &str) -> StrResult<String> {
|
||||
|
||||
let name = parts.next().ok_or("link is missing first part")?;
|
||||
let value = focus.field(name)?;
|
||||
let Some(category) = focus.scope().get_category(name) else {
|
||||
bail!("{name} has no category");
|
||||
};
|
||||
|
||||
// Handle grouped functions.
|
||||
if let Some(group) = GROUPS
|
||||
.iter()
|
||||
.filter(|_| category == "math")
|
||||
.find(|group| group.functions.iter().any(|func| func == name))
|
||||
{
|
||||
let mut route =
|
||||
format!("/docs/reference/math/{}/#functions-{}", group.name, name);
|
||||
if let Some(group) = GROUPS.iter().find(|group| {
|
||||
group.category == category.name() && group.filter.iter().any(|func| func == name)
|
||||
}) {
|
||||
let mut route = format!(
|
||||
"/docs/reference/{}/{}/#functions-{}",
|
||||
group.category, group.name, name
|
||||
);
|
||||
if let Some(param) = parts.next() {
|
||||
route.push('-');
|
||||
route.push_str(param);
|
||||
@ -81,7 +87,7 @@ fn resolve_definition(head: &str) -> StrResult<String> {
|
||||
return Ok(route);
|
||||
}
|
||||
|
||||
let mut route = format!("/docs/reference/{category}/{name}/");
|
||||
let mut route = format!("/docs/reference/{}/{name}/", category.name());
|
||||
if let Some(next) = parts.next() {
|
||||
if value.field(next).is_ok() {
|
||||
route.push_str("#definitions-");
|
||||
|
@ -62,7 +62,8 @@ pub enum BodyModel {
|
||||
/// Details about a category.
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct CategoryModel {
|
||||
pub name: EcoString,
|
||||
pub name: &'static str,
|
||||
pub title: &'static str,
|
||||
pub details: Html,
|
||||
pub items: Vec<CategoryItem>,
|
||||
pub shorthands: Option<ShorthandsModel>,
|
||||
@ -144,7 +145,8 @@ pub struct TypeModel {
|
||||
/// A collection of symbols.
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct SymbolsModel {
|
||||
pub name: &'static str,
|
||||
pub name: EcoString,
|
||||
pub title: EcoString,
|
||||
pub details: Html,
|
||||
pub list: Vec<SymbolModel>,
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
use comemo::Track;
|
||||
use ecow::{eco_vec, EcoString, EcoVec};
|
||||
use typst::doc::Frame;
|
||||
use typst::eval::{Route, Scopes, Tracer, Value, Vm};
|
||||
use typst::model::{DelayedErrors, Introspector, Label, Locator, Vt};
|
||||
use typst::diag::DelayedErrors;
|
||||
use typst::eval::{Route, Tracer, Vm};
|
||||
use typst::foundations::{Label, Scopes, Value};
|
||||
use typst::introspection::{Introspector, Locator};
|
||||
use typst::layout::{Frame, Vt};
|
||||
use typst::model::BibliographyElem;
|
||||
use typst::syntax::{ast, LinkedNode, Span, SyntaxKind};
|
||||
use typst::World;
|
||||
|
||||
@ -75,13 +78,9 @@ pub fn analyze_import(world: &dyn World, source: &LinkedNode) -> Option<Value> {
|
||||
/// - All labels and descriptions for them, if available
|
||||
/// - A split offset: All labels before this offset belong to nodes, all after
|
||||
/// belong to a bibliography.
|
||||
pub fn analyze_labels(
|
||||
world: &dyn World,
|
||||
frames: &[Frame],
|
||||
) -> (Vec<(Label, Option<EcoString>)>, usize) {
|
||||
pub fn analyze_labels(frames: &[Frame]) -> (Vec<(Label, Option<EcoString>)>, usize) {
|
||||
let mut output = vec![];
|
||||
let introspector = Introspector::new(frames);
|
||||
let items = &world.library().items;
|
||||
|
||||
// Labels in the document.
|
||||
for elem in introspector.all() {
|
||||
@ -102,7 +101,7 @@ pub fn analyze_labels(
|
||||
let split = output.len();
|
||||
|
||||
// Bibliography keys.
|
||||
for (key, detail) in (items.bibliography_keys)(introspector.track()) {
|
||||
for (key, detail) in BibliographyElem::keys(introspector.track()) {
|
||||
output.push((Label::new(&key), detail));
|
||||
}
|
||||
|
||||
|
@ -4,21 +4,21 @@ use std::collections::{BTreeSet, HashSet};
|
||||
use ecow::{eco_format, EcoString};
|
||||
use if_chain::if_chain;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typst::doc::Frame;
|
||||
use typst::eval::{
|
||||
format_str, repr, AutoValue, CastInfo, Func, Library, NoneValue, Repr, Scope, Type,
|
||||
Value,
|
||||
use typst::foundations::{
|
||||
fields_on, format_str, mutable_methods_on, repr, AutoValue, CastInfo, Func, Label,
|
||||
NoneValue, Repr, Scope, Type, Value,
|
||||
};
|
||||
use typst::geom::Color;
|
||||
use typst::model::Label;
|
||||
use typst::layout::Frame;
|
||||
use typst::syntax::{
|
||||
ast, is_id_continue, is_id_start, is_ident, LinkedNode, Source, SyntaxKind,
|
||||
};
|
||||
use typst::text::RawElem;
|
||||
use typst::visualize::Color;
|
||||
use typst::World;
|
||||
use unscanny::Scanner;
|
||||
|
||||
use crate::analyze::analyze_labels;
|
||||
use crate::{analyze_expr, analyze_import, plain_docs_sentence, summarize_font_family};
|
||||
use crate::analyze::{analyze_expr, analyze_import, analyze_labels};
|
||||
use crate::{plain_docs_sentence, summarize_font_family};
|
||||
|
||||
/// Autocomplete a cursor position in a source file.
|
||||
///
|
||||
@ -367,7 +367,7 @@ fn field_access_completions(ctx: &mut CompletionContext, value: &Value) {
|
||||
}
|
||||
}
|
||||
|
||||
for &(method, args) in typst::eval::mutable_methods_on(value.ty()) {
|
||||
for &(method, args) in mutable_methods_on(value.ty()) {
|
||||
ctx.completions.push(Completion {
|
||||
kind: CompletionKind::Func,
|
||||
label: method.into(),
|
||||
@ -380,7 +380,7 @@ fn field_access_completions(ctx: &mut CompletionContext, value: &Value) {
|
||||
})
|
||||
}
|
||||
|
||||
for &field in typst::eval::fields_on(value.ty()) {
|
||||
for &field in fields_on(value.ty()) {
|
||||
// Complete the field name along with its value. Notes:
|
||||
// 1. No parentheses since function fields cannot currently be called
|
||||
// with method syntax;
|
||||
@ -967,7 +967,6 @@ fn code_completions(ctx: &mut CompletionContext, hash: bool) {
|
||||
struct CompletionContext<'a> {
|
||||
world: &'a (dyn World + 'a),
|
||||
frames: &'a [Frame],
|
||||
library: &'a Library,
|
||||
global: &'a Scope,
|
||||
math: &'a Scope,
|
||||
text: &'a str,
|
||||
@ -996,7 +995,6 @@ impl<'a> CompletionContext<'a> {
|
||||
Some(Self {
|
||||
world,
|
||||
frames,
|
||||
library,
|
||||
global: library.global.scope(),
|
||||
math: library.math.scope(),
|
||||
text,
|
||||
@ -1074,7 +1072,7 @@ impl<'a> CompletionContext<'a> {
|
||||
|
||||
/// Add completions for raw block tags.
|
||||
fn raw_completions(&mut self) {
|
||||
for (name, mut tags) in (self.library.items.raw_languages)() {
|
||||
for (name, mut tags) in RawElem::languages() {
|
||||
let lower = name.to_lowercase();
|
||||
if !tags.contains(&lower.as_str()) {
|
||||
tags.push(lower.as_str());
|
||||
@ -1096,7 +1094,7 @@ impl<'a> CompletionContext<'a> {
|
||||
|
||||
/// Add completions for labels and references.
|
||||
fn label_completions(&mut self) {
|
||||
let (labels, split) = analyze_labels(self.world, self.frames);
|
||||
let (labels, split) = analyze_labels(self.frames);
|
||||
|
||||
let head = &self.text[..self.from];
|
||||
let at = head.ends_with('@');
|
||||
|
@ -1,10 +1,11 @@
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use ecow::EcoString;
|
||||
use typst::doc::{Destination, Frame, FrameItem, Meta, Position};
|
||||
use typst::geom::{Geometry, Point, Size};
|
||||
use typst::model::Introspector;
|
||||
use typst::introspection::{Introspector, Meta};
|
||||
use typst::layout::{Frame, FrameItem, Point, Position, Size};
|
||||
use typst::model::Destination;
|
||||
use typst::syntax::{FileId, LinkedNode, Source, Span, SyntaxKind};
|
||||
use typst::visualize::Geometry;
|
||||
use typst::World;
|
||||
|
||||
/// Where to [jump](jump_from_click) to.
|
||||
|
@ -13,9 +13,7 @@ pub use self::tooltip::{tooltip, Tooltip};
|
||||
use std::fmt::Write;
|
||||
|
||||
use ecow::{eco_format, EcoString};
|
||||
use typst::font::{FontInfo, FontStyle};
|
||||
|
||||
use self::analyze::*;
|
||||
use typst::text::{FontInfo, FontStyle};
|
||||
|
||||
/// Extract the first sentence of plain text of a piece of documentation.
|
||||
///
|
||||
|
@ -2,14 +2,15 @@ use std::fmt::Write;
|
||||
|
||||
use ecow::{eco_format, EcoString};
|
||||
use if_chain::if_chain;
|
||||
use typst::doc::Frame;
|
||||
use typst::eval::{repr, CapturesVisitor, CastInfo, Repr, Tracer, Value};
|
||||
use typst::geom::{round_2, Length, Numeric};
|
||||
use typst::eval::{CapturesVisitor, Tracer};
|
||||
use typst::foundations::{repr, CastInfo, Repr, Value};
|
||||
use typst::layout::{Frame, Length};
|
||||
use typst::syntax::{ast, LinkedNode, Source, SyntaxKind};
|
||||
use typst::util::{round_2, Numeric};
|
||||
use typst::World;
|
||||
|
||||
use crate::analyze::analyze_labels;
|
||||
use crate::{analyze_expr, plain_docs_sentence, summarize_font_family};
|
||||
use crate::analyze::{analyze_expr, analyze_labels};
|
||||
use crate::{plain_docs_sentence, summarize_font_family};
|
||||
|
||||
/// Describe the item under the cursor.
|
||||
pub fn tooltip(
|
||||
@ -25,7 +26,7 @@ pub fn tooltip(
|
||||
|
||||
named_param_tooltip(world, &leaf)
|
||||
.or_else(|| font_tooltip(world, &leaf))
|
||||
.or_else(|| label_tooltip(world, frames, &leaf))
|
||||
.or_else(|| label_tooltip(frames, &leaf))
|
||||
.or_else(|| expr_tooltip(world, &leaf))
|
||||
.or_else(|| closure_tooltip(&leaf))
|
||||
}
|
||||
@ -144,18 +145,14 @@ fn length_tooltip(length: Length) -> Option<Tooltip> {
|
||||
}
|
||||
|
||||
/// Tooltip for a hovered reference or label.
|
||||
fn label_tooltip(
|
||||
world: &dyn World,
|
||||
frames: &[Frame],
|
||||
leaf: &LinkedNode,
|
||||
) -> Option<Tooltip> {
|
||||
fn label_tooltip(frames: &[Frame], leaf: &LinkedNode) -> Option<Tooltip> {
|
||||
let target = match leaf.kind() {
|
||||
SyntaxKind::RefMarker => leaf.text().trim_start_matches('@'),
|
||||
SyntaxKind::Label => leaf.text().trim_start_matches('<').trim_end_matches('>'),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
for (label, detail) in analyze_labels(world, frames).0 {
|
||||
for (label, detail) in analyze_labels(frames).0 {
|
||||
if label.as_str() == target {
|
||||
return Some(Tooltip::Text(detail?));
|
||||
}
|
||||
|
@ -1,53 +0,0 @@
|
||||
[package]
|
||||
name = "typst-library"
|
||||
description = "The standard library for Typst."
|
||||
version.workspace = true
|
||||
rust-version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
categories.workspace = true
|
||||
keywords.workspace = true
|
||||
|
||||
[lib]
|
||||
test = false
|
||||
doctest = false
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
typst = { workspace = true }
|
||||
az = { workspace = true }
|
||||
chinese-number = { workspace = true }
|
||||
ciborium = { workspace = true }
|
||||
comemo = { workspace = true }
|
||||
csv = { workspace = true }
|
||||
ecow = { workspace = true }
|
||||
hayagriva = { workspace = true }
|
||||
hypher = { workspace = true }
|
||||
icu_properties = { workspace = true }
|
||||
icu_provider = { workspace = true }
|
||||
icu_provider_adapters = { workspace = true }
|
||||
icu_provider_blob = { workspace = true }
|
||||
icu_segmenter = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
kurbo = { workspace = true }
|
||||
lipsum = { workspace = true }
|
||||
log = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
roxmltree = { workspace = true }
|
||||
rustybuzz = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde_yaml = { workspace = true }
|
||||
smallvec = { workspace = true }
|
||||
syntect = { workspace = true }
|
||||
time = { workspace = true }
|
||||
toml = { workspace = true, features = ["display"] }
|
||||
tracing = { workspace = true }
|
||||
ttf-parser = { workspace = true }
|
||||
typed-arena = { workspace = true }
|
||||
unicode-bidi = { workspace = true }
|
||||
unicode-math-class = { workspace = true }
|
||||
unicode-script = { workspace = true }
|
||||
unicode-segmentation = { workspace = true }
|
@ -1,609 +0,0 @@
|
||||
use typst::diag::{format_xml_like_error, FileError};
|
||||
use typst::eval::Bytes;
|
||||
use typst::syntax::is_newline;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Hook up all data loading definitions.
|
||||
pub(super) fn define(global: &mut Scope) {
|
||||
global.category("data-loading");
|
||||
global.define_func::<read>();
|
||||
global.define_func::<csv>();
|
||||
global.define_func::<json>();
|
||||
global.define_func::<toml>();
|
||||
global.define_func::<yaml>();
|
||||
global.define_func::<cbor>();
|
||||
global.define_func::<xml>();
|
||||
}
|
||||
|
||||
/// Reads plain text or data from a file.
|
||||
///
|
||||
/// By default, the file will be read as UTF-8 and returned as a [string]($str).
|
||||
///
|
||||
/// If you specify `{encoding: none}`, this returns raw [bytes]($bytes) instead.
|
||||
///
|
||||
/// # Example
|
||||
/// ```example
|
||||
/// An example for a HTML file: \
|
||||
/// #let text = read("data.html")
|
||||
/// #raw(text, lang: "html")
|
||||
///
|
||||
/// Raw bytes:
|
||||
/// #read("tiger.jpg", encoding: none)
|
||||
/// ```
|
||||
#[func]
|
||||
pub fn read(
|
||||
/// The virtual machine.
|
||||
vm: &mut Vm,
|
||||
/// Path to a file.
|
||||
path: Spanned<EcoString>,
|
||||
/// The encoding to read the file with.
|
||||
///
|
||||
/// If set to `{none}`, this function returns raw bytes.
|
||||
#[named]
|
||||
#[default(Some(Encoding::Utf8))]
|
||||
encoding: Option<Encoding>,
|
||||
) -> SourceResult<Readable> {
|
||||
let Spanned { v: path, span } = path;
|
||||
let id = vm.resolve_path(&path).at(span)?;
|
||||
let data = vm.world().file(id).at(span)?;
|
||||
Ok(match encoding {
|
||||
None => Readable::Bytes(data),
|
||||
Some(Encoding::Utf8) => Readable::Str(
|
||||
std::str::from_utf8(&data)
|
||||
.map_err(|_| "file is not valid utf-8")
|
||||
.at(span)?
|
||||
.into(),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
/// An encoding of a file.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
|
||||
pub enum Encoding {
|
||||
/// The Unicode UTF-8 encoding.
|
||||
Utf8,
|
||||
}
|
||||
|
||||
/// A value that can be read from a file.
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub enum Readable {
|
||||
/// A decoded string.
|
||||
Str(Str),
|
||||
/// Raw bytes.
|
||||
Bytes(Bytes),
|
||||
}
|
||||
|
||||
impl Readable {
|
||||
fn as_slice(&self) -> &[u8] {
|
||||
match self {
|
||||
Readable::Bytes(v) => v,
|
||||
Readable::Str(v) => v.as_bytes(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cast! {
|
||||
Readable,
|
||||
self => match self {
|
||||
Self::Str(v) => v.into_value(),
|
||||
Self::Bytes(v) => v.into_value(),
|
||||
},
|
||||
v: Str => Self::Str(v),
|
||||
v: Bytes => Self::Bytes(v),
|
||||
}
|
||||
|
||||
impl From<Readable> for Bytes {
|
||||
fn from(value: Readable) -> Self {
|
||||
match value {
|
||||
Readable::Bytes(v) => v,
|
||||
Readable::Str(v) => v.as_bytes().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads structured data from a CSV file.
|
||||
///
|
||||
/// The CSV file will be read and parsed into a 2-dimensional array of strings:
|
||||
/// Each row in the CSV file will be represented as an array of strings, and all
|
||||
/// rows will be collected into a single array. Header rows will not be
|
||||
/// stripped.
|
||||
///
|
||||
/// # Example
|
||||
/// ```example
|
||||
/// #let results = csv("data.csv")
|
||||
///
|
||||
/// #table(
|
||||
/// columns: 2,
|
||||
/// [*Condition*], [*Result*],
|
||||
/// ..results.flatten(),
|
||||
/// )
|
||||
/// ```
|
||||
#[func(scope, title = "CSV")]
|
||||
pub fn csv(
|
||||
/// The virtual machine.
|
||||
vm: &mut Vm,
|
||||
/// Path to a CSV file.
|
||||
path: Spanned<EcoString>,
|
||||
/// The delimiter that separates columns in the CSV file.
|
||||
/// Must be a single ASCII character.
|
||||
#[named]
|
||||
#[default]
|
||||
delimiter: Delimiter,
|
||||
) -> SourceResult<Array> {
|
||||
let Spanned { v: path, span } = path;
|
||||
let id = vm.resolve_path(&path).at(span)?;
|
||||
let data = vm.world().file(id).at(span)?;
|
||||
self::csv::decode(Spanned::new(Readable::Bytes(data), span), delimiter)
|
||||
}
|
||||
|
||||
#[scope]
|
||||
impl csv {
|
||||
/// Reads structured data from a CSV string/bytes.
|
||||
#[func(title = "Decode CSV")]
|
||||
pub fn decode(
|
||||
/// CSV data.
|
||||
data: Spanned<Readable>,
|
||||
/// The delimiter that separates columns in the CSV file.
|
||||
/// Must be a single ASCII character.
|
||||
#[named]
|
||||
#[default]
|
||||
delimiter: Delimiter,
|
||||
) -> SourceResult<Array> {
|
||||
let Spanned { v: data, span } = data;
|
||||
let mut builder = ::csv::ReaderBuilder::new();
|
||||
builder.has_headers(false);
|
||||
builder.delimiter(delimiter.0 as u8);
|
||||
let mut reader = builder.from_reader(data.as_slice());
|
||||
let mut array = Array::new();
|
||||
|
||||
for (line, result) in reader.records().enumerate() {
|
||||
// Original solution use line from error, but that is incorrect with
|
||||
// `has_headers` set to `false`. See issue:
|
||||
// https://github.com/BurntSushi/rust-csv/issues/184
|
||||
let line = line + 1; // Counting lines from 1
|
||||
let row = result.map_err(|err| format_csv_error(err, line)).at(span)?;
|
||||
let sub = row.into_iter().map(|field| field.into_value()).collect();
|
||||
array.push(Value::Array(sub))
|
||||
}
|
||||
|
||||
Ok(array)
|
||||
}
|
||||
}
|
||||
|
||||
/// The delimiter to use when parsing CSV files.
|
||||
pub struct Delimiter(char);
|
||||
|
||||
impl Default for Delimiter {
|
||||
fn default() -> Self {
|
||||
Self(',')
|
||||
}
|
||||
}
|
||||
|
||||
cast! {
|
||||
Delimiter,
|
||||
self => self.0.into_value(),
|
||||
v: EcoString => {
|
||||
let mut chars = v.chars();
|
||||
let first = chars.next().ok_or("delimiter must not be empty")?;
|
||||
if chars.next().is_some() {
|
||||
bail!("delimiter must be a single character");
|
||||
}
|
||||
|
||||
if !first.is_ascii() {
|
||||
bail!("delimiter must be an ASCII character");
|
||||
}
|
||||
|
||||
Self(first)
|
||||
},
|
||||
}
|
||||
|
||||
/// Format the user-facing CSV error message.
|
||||
fn format_csv_error(err: ::csv::Error, line: usize) -> EcoString {
|
||||
match err.kind() {
|
||||
::csv::ErrorKind::Utf8 { .. } => "file is not valid utf-8".into(),
|
||||
::csv::ErrorKind::UnequalLengths { expected_len, len, .. } => {
|
||||
eco_format!(
|
||||
"failed to parse CSV (found {len} instead of \
|
||||
{expected_len} fields in line {line})"
|
||||
)
|
||||
}
|
||||
_ => eco_format!("failed to parse CSV ({err})"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads structured data from a JSON file.
|
||||
///
|
||||
/// The file must contain a valid JSON object or array. JSON objects will be
|
||||
/// converted into Typst dictionaries, and JSON arrays will be converted into
|
||||
/// Typst arrays. Strings and booleans will be converted into the Typst
|
||||
/// equivalents, `null` will be converted into `{none}`, and numbers will be
|
||||
/// converted to floats or integers depending on whether they are whole numbers.
|
||||
///
|
||||
/// The function returns a dictionary or an array, depending on the JSON file.
|
||||
///
|
||||
/// The JSON files in the example contain objects with the keys `temperature`,
|
||||
/// `unit`, and `weather`.
|
||||
///
|
||||
/// # Example
|
||||
/// ```example
|
||||
/// #let forecast(day) = block[
|
||||
/// #box(square(
|
||||
/// width: 2cm,
|
||||
/// inset: 8pt,
|
||||
/// fill: if day.weather == "sunny" {
|
||||
/// yellow
|
||||
/// } else {
|
||||
/// aqua
|
||||
/// },
|
||||
/// align(
|
||||
/// bottom + right,
|
||||
/// strong(day.weather),
|
||||
/// ),
|
||||
/// ))
|
||||
/// #h(6pt)
|
||||
/// #set text(22pt, baseline: -8pt)
|
||||
/// #day.temperature °#day.unit
|
||||
/// ]
|
||||
///
|
||||
/// #forecast(json("monday.json"))
|
||||
/// #forecast(json("tuesday.json"))
|
||||
/// ```
|
||||
#[func(scope, title = "JSON")]
|
||||
pub fn json(
|
||||
/// The virtual machine.
|
||||
vm: &mut Vm,
|
||||
/// Path to a JSON file.
|
||||
path: Spanned<EcoString>,
|
||||
) -> SourceResult<Value> {
|
||||
let Spanned { v: path, span } = path;
|
||||
let id = vm.resolve_path(&path).at(span)?;
|
||||
let data = vm.world().file(id).at(span)?;
|
||||
json::decode(Spanned::new(Readable::Bytes(data), span))
|
||||
}
|
||||
|
||||
#[scope]
|
||||
impl json {
|
||||
/// Reads structured data from a JSON string/bytes.
|
||||
#[func(title = "Decode JSON")]
|
||||
pub fn decode(
|
||||
/// JSON data.
|
||||
data: Spanned<Readable>,
|
||||
) -> SourceResult<Value> {
|
||||
let Spanned { v: data, span } = data;
|
||||
serde_json::from_slice(data.as_slice())
|
||||
.map_err(|err| eco_format!("failed to parse JSON ({err})"))
|
||||
.at(span)
|
||||
}
|
||||
|
||||
/// Encodes structured data into a JSON string.
|
||||
#[func(title = "Encode JSON")]
|
||||
pub fn encode(
|
||||
/// Value to be encoded.
|
||||
value: Spanned<Value>,
|
||||
/// Whether to pretty print the JSON with newlines and indentation.
|
||||
#[named]
|
||||
#[default(true)]
|
||||
pretty: bool,
|
||||
) -> SourceResult<Str> {
|
||||
let Spanned { v: value, span } = value;
|
||||
if pretty {
|
||||
serde_json::to_string_pretty(&value)
|
||||
} else {
|
||||
serde_json::to_string(&value)
|
||||
}
|
||||
.map(|v| v.into())
|
||||
.map_err(|err| eco_format!("failed to encode value as JSON ({err})"))
|
||||
.at(span)
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads structured data from a TOML file.
|
||||
///
|
||||
/// The file must contain a valid TOML table. TOML tables will be converted into
|
||||
/// Typst dictionaries, and TOML arrays will be converted into Typst arrays.
|
||||
/// Strings, booleans and datetimes will be converted into the Typst equivalents
|
||||
/// and numbers will be converted to floats or integers depending on whether
|
||||
/// they are whole numbers.
|
||||
///
|
||||
/// The TOML file in the example consists of a table with the keys `title`,
|
||||
/// `version`, and `authors`.
|
||||
///
|
||||
/// # Example
|
||||
/// ```example
|
||||
/// #let details = toml("details.toml")
|
||||
///
|
||||
/// Title: #details.title \
|
||||
/// Version: #details.version \
|
||||
/// Authors: #(details.authors
|
||||
/// .join(", ", last: " and "))
|
||||
/// ```
|
||||
#[func(scope, title = "TOML")]
|
||||
pub fn toml(
|
||||
/// The virtual machine.
|
||||
vm: &mut Vm,
|
||||
/// Path to a TOML file.
|
||||
path: Spanned<EcoString>,
|
||||
) -> SourceResult<Value> {
|
||||
let Spanned { v: path, span } = path;
|
||||
let id = vm.resolve_path(&path).at(span)?;
|
||||
let data = vm.world().file(id).at(span)?;
|
||||
toml::decode(Spanned::new(Readable::Bytes(data), span))
|
||||
}
|
||||
|
||||
#[scope]
|
||||
impl toml {
|
||||
/// Reads structured data from a TOML string/bytes.
|
||||
#[func(title = "Decode TOML")]
|
||||
pub fn decode(
|
||||
/// TOML data.
|
||||
data: Spanned<Readable>,
|
||||
) -> SourceResult<Value> {
|
||||
let Spanned { v: data, span } = data;
|
||||
let raw = std::str::from_utf8(data.as_slice())
|
||||
.map_err(|_| "file is not valid utf-8")
|
||||
.at(span)?;
|
||||
::toml::from_str(raw)
|
||||
.map_err(|err| format_toml_error(err, raw))
|
||||
.at(span)
|
||||
}
|
||||
|
||||
/// Encodes structured data into a TOML string.
|
||||
#[func(title = "Encode TOML")]
|
||||
pub fn encode(
|
||||
/// Value to be encoded.
|
||||
value: Spanned<Value>,
|
||||
/// Whether to pretty-print the resulting TOML.
|
||||
#[named]
|
||||
#[default(true)]
|
||||
pretty: bool,
|
||||
) -> SourceResult<Str> {
|
||||
let Spanned { v: value, span } = value;
|
||||
if pretty { ::toml::to_string_pretty(&value) } else { ::toml::to_string(&value) }
|
||||
.map(|v| v.into())
|
||||
.map_err(|err| eco_format!("failed to encode value as TOML ({err})"))
|
||||
.at(span)
|
||||
}
|
||||
}
|
||||
|
||||
/// Format the user-facing TOML error message.
|
||||
fn format_toml_error(error: ::toml::de::Error, raw: &str) -> EcoString {
|
||||
if let Some(head) = error.span().and_then(|range| raw.get(..range.start)) {
|
||||
let line = head.lines().count();
|
||||
let column = 1 + head.chars().rev().take_while(|&c| !is_newline(c)).count();
|
||||
eco_format!(
|
||||
"failed to parse TOML ({} at line {line} column {column})",
|
||||
error.message(),
|
||||
)
|
||||
} else {
|
||||
eco_format!("failed to parse TOML ({})", error.message())
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads structured data from a YAML file.
|
||||
///
|
||||
/// The file must contain a valid YAML object or array. YAML mappings will be
|
||||
/// converted into Typst dictionaries, and YAML sequences will be converted into
|
||||
/// Typst arrays. Strings and booleans will be converted into the Typst
|
||||
/// equivalents, null-values (`null`, `~` or empty ``) will be converted into
|
||||
/// `{none}`, and numbers will be converted to floats or integers depending on
|
||||
/// whether they are whole numbers. Custom YAML tags are ignored, though the
|
||||
/// loaded value will still be present.
|
||||
///
|
||||
/// The YAML files in the example contain objects with authors as keys,
|
||||
/// each with a sequence of their own submapping with the keys
|
||||
/// "title" and "published"
|
||||
///
|
||||
/// # Example
|
||||
/// ```example
|
||||
/// #let bookshelf(contents) = {
|
||||
/// for (author, works) in contents {
|
||||
/// author
|
||||
/// for work in works [
|
||||
/// - #work.title (#work.published)
|
||||
/// ]
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #bookshelf(
|
||||
/// yaml("scifi-authors.yaml")
|
||||
/// )
|
||||
/// ```
|
||||
#[func(scope, title = "YAML")]
|
||||
pub fn yaml(
|
||||
/// The virtual machine.
|
||||
vm: &mut Vm,
|
||||
/// Path to a YAML file.
|
||||
path: Spanned<EcoString>,
|
||||
) -> SourceResult<Value> {
|
||||
let Spanned { v: path, span } = path;
|
||||
let id = vm.resolve_path(&path).at(span)?;
|
||||
let data = vm.world().file(id).at(span)?;
|
||||
yaml::decode(Spanned::new(Readable::Bytes(data), span))
|
||||
}
|
||||
|
||||
#[scope]
|
||||
impl yaml {
|
||||
/// Reads structured data from a YAML string/bytes.
|
||||
#[func(title = "Decode YAML")]
|
||||
pub fn decode(
|
||||
/// YAML data.
|
||||
data: Spanned<Readable>,
|
||||
) -> SourceResult<Value> {
|
||||
let Spanned { v: data, span } = data;
|
||||
serde_yaml::from_slice(data.as_slice())
|
||||
.map_err(|err| eco_format!("failed to parse YAML ({err})"))
|
||||
.at(span)
|
||||
}
|
||||
|
||||
/// Encode structured data into a YAML string.
|
||||
#[func(title = "Encode YAML")]
|
||||
pub fn encode(
|
||||
/// Value to be encoded.
|
||||
value: Spanned<Value>,
|
||||
) -> SourceResult<Str> {
|
||||
let Spanned { v: value, span } = value;
|
||||
serde_yaml::to_string(&value)
|
||||
.map(|v| v.into())
|
||||
.map_err(|err| eco_format!("failed to encode value as YAML ({err})"))
|
||||
.at(span)
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads structured data from a CBOR file.
|
||||
///
|
||||
/// The file must contain a valid cbor serialization. Mappings will be
|
||||
/// converted into Typst dictionaries, and sequences will be converted into
|
||||
/// Typst arrays. Strings and booleans will be converted into the Typst
|
||||
/// equivalents, null-values (`null`, `~` or empty ``) will be converted into
|
||||
/// `{none}`, and numbers will be converted to floats or integers depending on
|
||||
/// whether they are whole numbers.
|
||||
#[func(scope, title = "CBOR")]
|
||||
pub fn cbor(
|
||||
/// The virtual machine.
|
||||
vm: &mut Vm,
|
||||
/// Path to a CBOR file.
|
||||
path: Spanned<EcoString>,
|
||||
) -> SourceResult<Value> {
|
||||
let Spanned { v: path, span } = path;
|
||||
let id = vm.resolve_path(&path).at(span)?;
|
||||
let data = vm.world().file(id).at(span)?;
|
||||
cbor::decode(Spanned::new(data, span))
|
||||
}
|
||||
|
||||
#[scope]
|
||||
impl cbor {
|
||||
/// Reads structured data from CBOR bytes.
|
||||
#[func(title = "Decode CBOR")]
|
||||
pub fn decode(
|
||||
/// cbor data.
|
||||
data: Spanned<Bytes>,
|
||||
) -> SourceResult<Value> {
|
||||
let Spanned { v: data, span } = data;
|
||||
ciborium::from_reader(data.as_slice())
|
||||
.map_err(|err| eco_format!("failed to parse CBOR ({err})"))
|
||||
.at(span)
|
||||
}
|
||||
|
||||
/// Encode structured data into CBOR bytes.
|
||||
#[func(title = "Encode CBOR")]
|
||||
pub fn encode(
|
||||
/// Value to be encoded.
|
||||
value: Spanned<Value>,
|
||||
) -> SourceResult<Bytes> {
|
||||
let Spanned { v: value, span } = value;
|
||||
let mut res = Vec::new();
|
||||
ciborium::into_writer(&value, &mut res)
|
||||
.map(|_| res.into())
|
||||
.map_err(|err| eco_format!("failed to encode value as CBOR ({err})"))
|
||||
.at(span)
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads structured data from an XML file.
|
||||
///
|
||||
/// The XML file is parsed into an array of dictionaries and strings. XML nodes
|
||||
/// can be elements or strings. Elements are represented as dictionaries with
|
||||
/// the the following keys:
|
||||
///
|
||||
/// - `tag`: The name of the element as a string.
|
||||
/// - `attrs`: A dictionary of the element's attributes as strings.
|
||||
/// - `children`: An array of the element's child nodes.
|
||||
///
|
||||
/// The XML file in the example contains a root `news` tag with multiple
|
||||
/// `article` tags. Each article has a `title`, `author`, and `content` tag. The
|
||||
/// `content` tag contains one or more paragraphs, which are represented as `p`
|
||||
/// tags.
|
||||
///
|
||||
/// # Example
|
||||
/// ```example
|
||||
/// #let find-child(elem, tag) = {
|
||||
/// elem.children
|
||||
/// .find(e => "tag" in e and e.tag == tag)
|
||||
/// }
|
||||
///
|
||||
/// #let article(elem) = {
|
||||
/// let title = find-child(elem, "title")
|
||||
/// let author = find-child(elem, "author")
|
||||
/// let pars = find-child(elem, "content")
|
||||
///
|
||||
/// heading(title.children.first())
|
||||
/// text(10pt, weight: "medium")[
|
||||
/// Published by
|
||||
/// #author.children.first()
|
||||
/// ]
|
||||
///
|
||||
/// for p in pars.children {
|
||||
/// if (type(p) == "dictionary") {
|
||||
/// parbreak()
|
||||
/// p.children.first()
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #let data = xml("example.xml")
|
||||
/// #for elem in data.first().children {
|
||||
/// if (type(elem) == "dictionary") {
|
||||
/// article(elem)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[func(scope, title = "XML")]
|
||||
pub fn xml(
|
||||
/// The virtual machine.
|
||||
vm: &mut Vm,
|
||||
/// Path to an XML file.
|
||||
path: Spanned<EcoString>,
|
||||
) -> SourceResult<Value> {
|
||||
let Spanned { v: path, span } = path;
|
||||
let id = vm.resolve_path(&path).at(span)?;
|
||||
let data = vm.world().file(id).at(span)?;
|
||||
xml::decode(Spanned::new(Readable::Bytes(data), span))
|
||||
}
|
||||
|
||||
#[scope]
|
||||
impl xml {
|
||||
/// Reads structured data from an XML string/bytes.
|
||||
#[func(title = "Decode XML")]
|
||||
pub fn decode(
|
||||
/// XML data.
|
||||
data: Spanned<Readable>,
|
||||
) -> SourceResult<Value> {
|
||||
let Spanned { v: data, span } = data;
|
||||
let text = std::str::from_utf8(data.as_slice())
|
||||
.map_err(FileError::from)
|
||||
.at(span)?;
|
||||
let document =
|
||||
roxmltree::Document::parse(text).map_err(format_xml_error).at(span)?;
|
||||
Ok(convert_xml(document.root()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert an XML node to a Typst value.
|
||||
fn convert_xml(node: roxmltree::Node) -> Value {
|
||||
if node.is_text() {
|
||||
return node.text().unwrap_or_default().into_value();
|
||||
}
|
||||
|
||||
let children: Array = node.children().map(convert_xml).collect();
|
||||
if node.is_root() {
|
||||
return Value::Array(children);
|
||||
}
|
||||
|
||||
let tag: Str = node.tag_name().name().into();
|
||||
let attrs: Dict = node
|
||||
.attributes()
|
||||
.map(|attr| (attr.name().into(), attr.value().into_value()))
|
||||
.collect();
|
||||
|
||||
Value::Dict(dict! {
|
||||
"tag" => tag,
|
||||
"attrs" => attrs,
|
||||
"children" => children,
|
||||
})
|
||||
}
|
||||
|
||||
/// Format the user-facing XML error message.
|
||||
fn format_xml_error(error: roxmltree::Error) -> EcoString {
|
||||
format_xml_like_error("XML", error)
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
//! Computational functions.
|
||||
|
||||
pub mod calc;
|
||||
pub mod sys;
|
||||
|
||||
mod data;
|
||||
mod foundations;
|
||||
|
||||
pub use self::data::*;
|
||||
pub use self::foundations::*;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Hook up all compute definitions.
|
||||
pub(super) fn define(global: &mut Scope) {
|
||||
self::foundations::define(global);
|
||||
self::data::define(global);
|
||||
self::calc::define(global);
|
||||
self::sys::define(global);
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Aligns content horizontally and vertically.
|
||||
///
|
||||
/// # Example
|
||||
/// ```example
|
||||
/// #set align(center)
|
||||
///
|
||||
/// Centered text, a sight to see \
|
||||
/// In perfect balance, visually \
|
||||
/// Not left nor right, it stands alone \
|
||||
/// A work of art, a visual throne
|
||||
/// ```
|
||||
#[elem(Show)]
|
||||
pub struct AlignElem {
|
||||
/// The [alignment]($alignment) along both axes.
|
||||
///
|
||||
/// ```example
|
||||
/// #set page(height: 6cm)
|
||||
/// #set text(lang: "ar")
|
||||
///
|
||||
/// مثال
|
||||
/// #align(
|
||||
/// end + horizon,
|
||||
/// rect(inset: 12pt)[ركن]
|
||||
/// )
|
||||
/// ```
|
||||
#[positional]
|
||||
#[fold]
|
||||
#[default]
|
||||
pub alignment: Align,
|
||||
|
||||
/// The content to align.
|
||||
#[required]
|
||||
pub body: Content,
|
||||
}
|
||||
|
||||
impl Show for AlignElem {
|
||||
#[tracing::instrument(name = "AlignElem::show", skip_all)]
|
||||
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||
Ok(self
|
||||
.body()
|
||||
.clone()
|
||||
.styled(Self::set_alignment(self.alignment(styles))))
|
||||
}
|
||||
}
|
@ -1,170 +0,0 @@
|
||||
//! Typst's standard library.
|
||||
|
||||
#![allow(clippy::wildcard_in_or_patterns)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
#![allow(clippy::comparison_chain)]
|
||||
|
||||
pub mod compute;
|
||||
pub mod layout;
|
||||
pub mod math;
|
||||
pub mod meta;
|
||||
pub mod prelude;
|
||||
pub mod shared;
|
||||
pub mod symbols;
|
||||
pub mod text;
|
||||
pub mod visualize;
|
||||
|
||||
use typst::eval::{Array, LangItems, Library, Module, Scope, Smart};
|
||||
use typst::geom::{Align, Color, Dir};
|
||||
use typst::model::{NativeElement, Styles};
|
||||
|
||||
use self::layout::LayoutRoot;
|
||||
|
||||
/// Construct the standard library.
|
||||
pub fn build() -> Library {
|
||||
let math = math::module();
|
||||
let global = global(math.clone());
|
||||
Library { global, math, styles: styles(), items: items() }
|
||||
}
|
||||
|
||||
/// Construct the module with global definitions.
|
||||
#[tracing::instrument(skip_all)]
|
||||
fn global(math: Module) -> Module {
|
||||
let mut global = Scope::deduplicating();
|
||||
text::define(&mut global);
|
||||
global.define_module(math);
|
||||
layout::define(&mut global);
|
||||
visualize::define(&mut global);
|
||||
meta::define(&mut global);
|
||||
symbols::define(&mut global);
|
||||
compute::define(&mut global);
|
||||
prelude(&mut global);
|
||||
Module::new("global", global)
|
||||
}
|
||||
|
||||
/// Defines scoped values that are globally available, too.
|
||||
fn prelude(global: &mut Scope) {
|
||||
global.reset_category();
|
||||
global.define("black", Color::BLACK);
|
||||
global.define("gray", Color::GRAY);
|
||||
global.define("silver", Color::SILVER);
|
||||
global.define("white", Color::WHITE);
|
||||
global.define("navy", Color::NAVY);
|
||||
global.define("blue", Color::BLUE);
|
||||
global.define("aqua", Color::AQUA);
|
||||
global.define("teal", Color::TEAL);
|
||||
global.define("eastern", Color::EASTERN);
|
||||
global.define("purple", Color::PURPLE);
|
||||
global.define("fuchsia", Color::FUCHSIA);
|
||||
global.define("maroon", Color::MAROON);
|
||||
global.define("red", Color::RED);
|
||||
global.define("orange", Color::ORANGE);
|
||||
global.define("yellow", Color::YELLOW);
|
||||
global.define("olive", Color::OLIVE);
|
||||
global.define("green", Color::GREEN);
|
||||
global.define("lime", Color::LIME);
|
||||
global.define("luma", Color::luma_data());
|
||||
global.define("oklab", Color::oklab_data());
|
||||
global.define("oklch", Color::oklch_data());
|
||||
global.define("rgb", Color::rgb_data());
|
||||
global.define("cmyk", Color::cmyk_data());
|
||||
global.define("range", Array::range_data());
|
||||
global.define("ltr", Dir::LTR);
|
||||
global.define("rtl", Dir::RTL);
|
||||
global.define("ttb", Dir::TTB);
|
||||
global.define("btt", Dir::BTT);
|
||||
global.define("start", Align::START);
|
||||
global.define("left", Align::LEFT);
|
||||
global.define("center", Align::CENTER);
|
||||
global.define("right", Align::RIGHT);
|
||||
global.define("end", Align::END);
|
||||
global.define("top", Align::TOP);
|
||||
global.define("horizon", Align::HORIZON);
|
||||
global.define("bottom", Align::BOTTOM);
|
||||
}
|
||||
|
||||
/// Construct the standard style map.
|
||||
fn styles() -> Styles {
|
||||
Styles::new()
|
||||
}
|
||||
|
||||
/// Construct the standard lang item mapping.
|
||||
fn items() -> LangItems {
|
||||
LangItems {
|
||||
layout: |world, content, styles| content.layout_root(world, styles),
|
||||
em: text::TextElem::size_in,
|
||||
dir: text::TextElem::dir_in,
|
||||
space: || text::SpaceElem::new().pack(),
|
||||
linebreak: || text::LinebreakElem::new().pack(),
|
||||
text: |text| text::TextElem::new(text).pack(),
|
||||
text_elem: text::TextElem::elem(),
|
||||
text_str: |content| Some(content.to::<text::TextElem>()?.text()),
|
||||
smart_quote: |double| text::SmartquoteElem::new().with_double(double).pack(),
|
||||
parbreak: || layout::ParbreakElem::new().pack(),
|
||||
strong: |body| text::StrongElem::new(body).pack(),
|
||||
emph: |body| text::EmphElem::new(body).pack(),
|
||||
raw: |text, lang, block| {
|
||||
let mut elem = text::RawElem::new(text).with_block(block);
|
||||
if let Some(lang) = lang {
|
||||
elem.push_lang(Some(lang));
|
||||
}
|
||||
elem.pack()
|
||||
},
|
||||
raw_languages: text::RawElem::languages,
|
||||
link: |url| meta::LinkElem::from_url(url).pack(),
|
||||
reference: |target, supplement| {
|
||||
let mut elem = meta::RefElem::new(target);
|
||||
if let Some(supplement) = supplement {
|
||||
elem.push_supplement(Smart::Custom(Some(meta::Supplement::Content(
|
||||
supplement,
|
||||
))));
|
||||
}
|
||||
elem.pack()
|
||||
},
|
||||
bibliography_keys: meta::BibliographyElem::keys,
|
||||
heading: |level, title| meta::HeadingElem::new(title).with_level(level).pack(),
|
||||
heading_elem: meta::HeadingElem::elem(),
|
||||
list_item: |body| layout::ListItem::new(body).pack(),
|
||||
enum_item: |number, body| {
|
||||
let mut elem = layout::EnumItem::new(body);
|
||||
if let Some(number) = number {
|
||||
elem.push_number(Some(number));
|
||||
}
|
||||
elem.pack()
|
||||
},
|
||||
term_item: |term, description| layout::TermItem::new(term, description).pack(),
|
||||
equation: |body, block| math::EquationElem::new(body).with_block(block).pack(),
|
||||
math_align_point: || math::AlignPointElem::new().pack(),
|
||||
math_delimited: |open, body, close| math::LrElem::new(open + body + close).pack(),
|
||||
math_attach: |base, t, b, tl, bl, tr, br| {
|
||||
let mut elem = math::AttachElem::new(base);
|
||||
if let Some(t) = t {
|
||||
elem.push_t(Some(t));
|
||||
}
|
||||
if let Some(b) = b {
|
||||
elem.push_b(Some(b));
|
||||
}
|
||||
if let Some(tl) = tl {
|
||||
elem.push_tl(Some(tl));
|
||||
}
|
||||
if let Some(bl) = bl {
|
||||
elem.push_bl(Some(bl));
|
||||
}
|
||||
if let Some(tr) = tr {
|
||||
elem.push_tr(Some(tr));
|
||||
}
|
||||
if let Some(br) = br {
|
||||
elem.push_br(Some(br));
|
||||
}
|
||||
elem.pack()
|
||||
},
|
||||
math_primes: |count| math::PrimesElem::new(count).pack(),
|
||||
math_accent: |base, accent| {
|
||||
math::AccentElem::new(base, math::Accent::new(accent)).pack()
|
||||
},
|
||||
math_frac: |num, denom| math::FracElem::new(num, denom).pack(),
|
||||
math_root: |index, radicand| {
|
||||
math::RootElem::new(radicand).with_index(index).pack()
|
||||
},
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
//! Interaction between document parts.
|
||||
|
||||
mod bibliography;
|
||||
mod cite;
|
||||
mod context;
|
||||
mod counter;
|
||||
mod document;
|
||||
mod figure;
|
||||
mod footnote;
|
||||
mod heading;
|
||||
mod link;
|
||||
mod metadata;
|
||||
#[path = "numbering.rs"]
|
||||
mod numbering_;
|
||||
mod outline;
|
||||
#[path = "query.rs"]
|
||||
mod query_;
|
||||
mod reference;
|
||||
mod state;
|
||||
|
||||
pub use self::bibliography::*;
|
||||
pub use self::cite::*;
|
||||
pub use self::context::*;
|
||||
pub use self::counter::*;
|
||||
pub use self::document::*;
|
||||
pub use self::figure::*;
|
||||
pub use self::footnote::*;
|
||||
pub use self::heading::*;
|
||||
pub use self::link::*;
|
||||
pub use self::metadata::*;
|
||||
pub use self::numbering_::*;
|
||||
pub use self::outline::*;
|
||||
pub use self::query_::*;
|
||||
pub use self::reference::*;
|
||||
pub use self::state::*;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::text::TextElem;
|
||||
|
||||
/// Hook up all meta definitions.
|
||||
pub(super) fn define(global: &mut Scope) {
|
||||
global.category("meta");
|
||||
global.define_type::<Label>();
|
||||
global.define_type::<Selector>();
|
||||
global.define_type::<Location>();
|
||||
global.define_type::<Counter>();
|
||||
global.define_type::<State>();
|
||||
global.define_elem::<DocumentElem>();
|
||||
global.define_elem::<RefElem>();
|
||||
global.define_elem::<LinkElem>();
|
||||
global.define_elem::<OutlineElem>();
|
||||
global.define_elem::<HeadingElem>();
|
||||
global.define_elem::<FigureElem>();
|
||||
global.define_elem::<FootnoteElem>();
|
||||
global.define_elem::<CiteElem>();
|
||||
global.define_elem::<BibliographyElem>();
|
||||
global.define_elem::<MetadataElem>();
|
||||
global.define_func::<locate>();
|
||||
global.define_func::<style>();
|
||||
global.define_func::<layout>();
|
||||
global.define_func::<numbering>();
|
||||
global.define_func::<query>();
|
||||
}
|
||||
|
||||
/// An element that has a local name.
|
||||
pub trait LocalNameIn: LocalName {
|
||||
/// Gets the local name from the style chain.
|
||||
fn local_name_in(styles: StyleChain) -> &'static str
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Self::local_name(TextElem::lang_in(styles), TextElem::region_in(styles))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: LocalName> LocalNameIn for T {}
|
@ -1,42 +0,0 @@
|
||||
//! Helpful imports for creating library functionality.
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use std::fmt::{self, Debug, Formatter};
|
||||
#[doc(no_inline)]
|
||||
pub use std::num::NonZeroUsize;
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use comemo::{Track, Tracked, TrackedMut};
|
||||
#[doc(no_inline)]
|
||||
pub use ecow::{eco_format, EcoString};
|
||||
#[doc(no_inline)]
|
||||
pub use smallvec::{smallvec, SmallVec};
|
||||
#[doc(no_inline)]
|
||||
pub use typst::diag::{bail, error, At, Hint, SourceResult, StrResult};
|
||||
#[doc(no_inline)]
|
||||
pub use typst::doc::*;
|
||||
#[doc(no_inline)]
|
||||
pub use typst::eval::{
|
||||
array, cast, dict, format_str, func, scope, ty, Args, Array, Bytes, Cast, Dict,
|
||||
FromValue, Func, IntoValue, Repr, Scope, Smart, Str, Symbol, Type, Value, Vm,
|
||||
};
|
||||
#[doc(no_inline)]
|
||||
pub use typst::geom::*;
|
||||
#[doc(no_inline)]
|
||||
pub use typst::model::{
|
||||
elem, select_where, Behave, Behaviour, Construct, Content, Element, ElementFields,
|
||||
Finalize, Fold, Introspector, Label, LocalName, Locatable, LocatableSelector,
|
||||
Location, Locator, MetaElem, NativeElement, PlainText, Resolve, Selector, Set, Show,
|
||||
StyleChain, StyleVec, Styles, Synthesize, Unlabellable, Vt,
|
||||
};
|
||||
#[doc(no_inline)]
|
||||
pub use typst::syntax::{FileId, Span, Spanned};
|
||||
#[doc(no_inline)]
|
||||
pub use typst::util::NonZeroExt;
|
||||
#[doc(no_inline)]
|
||||
pub use typst::World;
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use crate::layout::{Fragment, Layout, Regions};
|
||||
#[doc(no_inline)]
|
||||
pub use crate::shared::{ContentExt, StylesExt};
|
@ -1,92 +0,0 @@
|
||||
//! Extension traits.
|
||||
|
||||
use crate::layout::{AlignElem, MoveElem, PadElem};
|
||||
use crate::prelude::*;
|
||||
use crate::text::{EmphElem, FontFamily, FontList, StrongElem, TextElem, UnderlineElem};
|
||||
|
||||
/// Additional methods on content.
|
||||
pub trait ContentExt {
|
||||
/// Make this content strong.
|
||||
fn strong(self) -> Self;
|
||||
|
||||
/// Make this content emphasized.
|
||||
fn emph(self) -> Self;
|
||||
|
||||
/// Underline this content.
|
||||
fn underlined(self) -> Self;
|
||||
|
||||
/// Link the content somewhere.
|
||||
fn linked(self, dest: Destination) -> Self;
|
||||
|
||||
/// Make the content linkable by `.linked(Destination::Location(loc))`.
|
||||
///
|
||||
/// Should be used in combination with [`Location::variant`].
|
||||
fn backlinked(self, loc: Location) -> Self;
|
||||
|
||||
/// Set alignments for this content.
|
||||
fn aligned(self, align: Align) -> Self;
|
||||
|
||||
/// Pad this content at the sides.
|
||||
fn padded(self, padding: Sides<Rel<Length>>) -> Self;
|
||||
|
||||
/// Transform this content's contents without affecting layout.
|
||||
fn moved(self, delta: Axes<Rel<Length>>) -> Self;
|
||||
}
|
||||
|
||||
impl ContentExt for Content {
|
||||
fn strong(self) -> Self {
|
||||
StrongElem::new(self).pack()
|
||||
}
|
||||
|
||||
fn emph(self) -> Self {
|
||||
EmphElem::new(self).pack()
|
||||
}
|
||||
|
||||
fn underlined(self) -> Self {
|
||||
UnderlineElem::new(self).pack()
|
||||
}
|
||||
|
||||
fn linked(self, dest: Destination) -> Self {
|
||||
self.styled(MetaElem::set_data(smallvec![Meta::Link(dest)]))
|
||||
}
|
||||
|
||||
fn backlinked(self, loc: Location) -> Self {
|
||||
let mut backlink = Content::empty();
|
||||
backlink.set_location(loc);
|
||||
self.styled(MetaElem::set_data(smallvec![Meta::Elem(backlink)]))
|
||||
}
|
||||
|
||||
fn aligned(self, align: Align) -> Self {
|
||||
self.styled(AlignElem::set_alignment(align))
|
||||
}
|
||||
|
||||
fn padded(self, padding: Sides<Rel<Length>>) -> Self {
|
||||
PadElem::new(self)
|
||||
.with_left(padding.left)
|
||||
.with_top(padding.top)
|
||||
.with_right(padding.right)
|
||||
.with_bottom(padding.bottom)
|
||||
.pack()
|
||||
}
|
||||
|
||||
fn moved(self, delta: Axes<Rel<Length>>) -> Self {
|
||||
MoveElem::new(self).with_dx(delta.x).with_dy(delta.y).pack()
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods for style lists.
|
||||
pub trait StylesExt {
|
||||
/// Set a font family composed of a preferred family and existing families
|
||||
/// from a style chain.
|
||||
fn set_family(&mut self, preferred: FontFamily, existing: StyleChain);
|
||||
}
|
||||
|
||||
impl StylesExt for Styles {
|
||||
fn set_family(&mut self, preferred: FontFamily, existing: StyleChain) {
|
||||
self.set(TextElem::set_font(FontList(
|
||||
std::iter::once(preferred)
|
||||
.chain(TextElem::font_in(existing).into_iter().cloned())
|
||||
.collect(),
|
||||
)));
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
//! Shared definitions for the standard library.
|
||||
|
||||
mod behave;
|
||||
mod ext;
|
||||
|
||||
pub use behave::*;
|
||||
pub use ext::*;
|
@ -1,17 +0,0 @@
|
||||
//! Modifiable symbols.
|
||||
|
||||
mod emoji;
|
||||
mod sym;
|
||||
|
||||
pub use emoji::*;
|
||||
pub use sym::*;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Hook up all symbol definitions.
|
||||
pub(super) fn define(global: &mut Scope) {
|
||||
global.category("symbols");
|
||||
global.define_type::<Symbol>();
|
||||
global.define_module(sym());
|
||||
global.define_module(emoji());
|
||||
}
|
@ -1,315 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use crate::text::TextElem;
|
||||
|
||||
/// A text space.
|
||||
#[elem(Behave, Unlabellable, PlainText, Repr)]
|
||||
pub struct SpaceElem {}
|
||||
|
||||
impl Repr for SpaceElem {
|
||||
fn repr(&self) -> EcoString {
|
||||
EcoString::inline("[ ]")
|
||||
}
|
||||
}
|
||||
|
||||
impl Behave for SpaceElem {
|
||||
fn behaviour(&self) -> Behaviour {
|
||||
Behaviour::Weak(2)
|
||||
}
|
||||
}
|
||||
|
||||
impl Unlabellable for SpaceElem {}
|
||||
|
||||
impl PlainText for SpaceElem {
|
||||
fn plain_text(&self, text: &mut EcoString) {
|
||||
text.push(' ');
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts a line break.
|
||||
///
|
||||
/// Advances the paragraph to the next line. A single trailing line break at the
|
||||
/// end of a paragraph is ignored, but more than one creates additional empty
|
||||
/// lines.
|
||||
///
|
||||
/// # Example
|
||||
/// ```example
|
||||
/// *Date:* 26.12.2022 \
|
||||
/// *Topic:* Infrastructure Test \
|
||||
/// *Severity:* High \
|
||||
/// ```
|
||||
///
|
||||
/// # Syntax
|
||||
/// This function also has dedicated syntax: To insert a line break, simply write
|
||||
/// a backslash followed by whitespace. This always creates an unjustified
|
||||
/// break.
|
||||
#[elem(title = "Line Break", Behave)]
|
||||
pub struct LinebreakElem {
|
||||
/// Whether to justify the line before the break.
|
||||
///
|
||||
/// This is useful if you found a better line break opportunity in your
|
||||
/// justified text than Typst did.
|
||||
///
|
||||
/// ```example
|
||||
/// #set par(justify: true)
|
||||
/// #let jb = linebreak(justify: true)
|
||||
///
|
||||
/// I have manually tuned the #jb
|
||||
/// line breaks in this paragraph #jb
|
||||
/// for an _interesting_ result. #jb
|
||||
/// ```
|
||||
#[default(false)]
|
||||
pub justify: bool,
|
||||
}
|
||||
|
||||
impl Behave for LinebreakElem {
|
||||
fn behaviour(&self) -> Behaviour {
|
||||
Behaviour::Destructive
|
||||
}
|
||||
}
|
||||
|
||||
/// Strongly emphasizes content by increasing the font weight.
|
||||
///
|
||||
/// Increases the current font weight by a given `delta`.
|
||||
///
|
||||
/// # Example
|
||||
/// ```example
|
||||
/// This is *strong.* \
|
||||
/// This is #strong[too.] \
|
||||
///
|
||||
/// #show strong: set text(red)
|
||||
/// And this is *evermore.*
|
||||
/// ```
|
||||
///
|
||||
/// # Syntax
|
||||
/// This function also has dedicated syntax: To strongly emphasize content,
|
||||
/// simply enclose it in stars/asterisks (`*`). Note that this only works at
|
||||
/// word boundaries. To strongly emphasize part of a word, you have to use the
|
||||
/// function.
|
||||
#[elem(title = "Strong Emphasis", Show)]
|
||||
pub struct StrongElem {
|
||||
/// The delta to apply on the font weight.
|
||||
///
|
||||
/// ```example
|
||||
/// #set strong(delta: 0)
|
||||
/// No *effect!*
|
||||
/// ```
|
||||
#[default(300)]
|
||||
pub delta: i64,
|
||||
|
||||
/// The content to strongly emphasize.
|
||||
#[required]
|
||||
pub body: Content,
|
||||
}
|
||||
|
||||
impl Show for StrongElem {
|
||||
#[tracing::instrument(name = "StrongElem::show", skip_all)]
|
||||
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||
Ok(self
|
||||
.body()
|
||||
.clone()
|
||||
.styled(TextElem::set_delta(Delta(self.delta(styles)))))
|
||||
}
|
||||
}
|
||||
|
||||
/// A delta that is summed up when folded.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Delta(pub i64);
|
||||
|
||||
cast! {
|
||||
Delta,
|
||||
self => self.0.into_value(),
|
||||
v: i64 => Self(v),
|
||||
}
|
||||
|
||||
impl Fold for Delta {
|
||||
type Output = i64;
|
||||
|
||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||
outer + self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Emphasizes content by setting it in italics.
|
||||
///
|
||||
/// - If the current [text style]($text.style) is `{"normal"}`, this turns it
|
||||
/// into `{"italic"}`.
|
||||
/// - If it is already `{"italic"}` or `{"oblique"}`, it turns it back to
|
||||
/// `{"normal"}`.
|
||||
///
|
||||
/// # Example
|
||||
/// ```example
|
||||
/// This is _emphasized._ \
|
||||
/// This is #emph[too.]
|
||||
///
|
||||
/// #show emph: it => {
|
||||
/// text(blue, it.body)
|
||||
/// }
|
||||
///
|
||||
/// This is _emphasized_ differently.
|
||||
/// ```
|
||||
///
|
||||
/// # Syntax
|
||||
/// This function also has dedicated syntax: To emphasize content, simply
|
||||
/// enclose it in underscores (`_`). Note that this only works at word
|
||||
/// boundaries. To emphasize part of a word, you have to use the function.
|
||||
#[elem(title = "Emphasis", Show)]
|
||||
pub struct EmphElem {
|
||||
/// The content to emphasize.
|
||||
#[required]
|
||||
pub body: Content,
|
||||
}
|
||||
|
||||
impl Show for EmphElem {
|
||||
#[tracing::instrument(name = "EmphElem::show", skip(self))]
|
||||
fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> {
|
||||
Ok(self.body().clone().styled(TextElem::set_emph(Toggle)))
|
||||
}
|
||||
}
|
||||
|
||||
/// A toggle that turns on and off alternatingly if folded.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Toggle;
|
||||
|
||||
cast! {
|
||||
Toggle,
|
||||
self => Value::None,
|
||||
_: Value => Self,
|
||||
}
|
||||
|
||||
impl Fold for Toggle {
|
||||
type Output = bool;
|
||||
|
||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||
!outer
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts text or content to lowercase.
|
||||
///
|
||||
/// # Example
|
||||
/// ```example
|
||||
/// #lower("ABC") \
|
||||
/// #lower[*My Text*] \
|
||||
/// #lower[already low]
|
||||
/// ```
|
||||
#[func(title = "Lowercase")]
|
||||
pub fn lower(
|
||||
/// The text to convert to lowercase.
|
||||
text: Caseable,
|
||||
) -> Caseable {
|
||||
case(text, Case::Lower)
|
||||
}
|
||||
|
||||
/// Converts text or content to uppercase.
|
||||
///
|
||||
/// # Example
|
||||
/// ```example
|
||||
/// #upper("abc") \
|
||||
/// #upper[*my text*] \
|
||||
/// #upper[ALREADY HIGH]
|
||||
/// ```
|
||||
#[func(title = "Uppercase")]
|
||||
pub fn upper(
|
||||
/// The text to convert to uppercase.
|
||||
text: Caseable,
|
||||
) -> Caseable {
|
||||
case(text, Case::Upper)
|
||||
}
|
||||
|
||||
/// Change the case of text.
|
||||
fn case(text: Caseable, case: Case) -> Caseable {
|
||||
match text {
|
||||
Caseable::Str(v) => Caseable::Str(case.apply(&v).into()),
|
||||
Caseable::Content(v) => {
|
||||
Caseable::Content(v.styled(TextElem::set_case(Some(case))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A value whose case can be changed.
|
||||
pub enum Caseable {
|
||||
Str(Str),
|
||||
Content(Content),
|
||||
}
|
||||
|
||||
cast! {
|
||||
Caseable,
|
||||
self => match self {
|
||||
Self::Str(v) => v.into_value(),
|
||||
Self::Content(v) => v.into_value(),
|
||||
},
|
||||
v: Str => Self::Str(v),
|
||||
v: Content => Self::Content(v),
|
||||
}
|
||||
|
||||
/// A case transformation on text.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
|
||||
pub enum Case {
|
||||
/// Everything is lowercased.
|
||||
Lower,
|
||||
/// Everything is uppercased.
|
||||
Upper,
|
||||
}
|
||||
|
||||
impl Case {
|
||||
/// Apply the case to a string.
|
||||
pub fn apply(self, text: &str) -> String {
|
||||
match self {
|
||||
Self::Lower => text.to_lowercase(),
|
||||
Self::Upper => text.to_uppercase(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Displays text in small capitals.
|
||||
///
|
||||
/// _Note:_ This enables the OpenType `smcp` feature for the font. Not all fonts
|
||||
/// support this feature. Sometimes smallcaps are part of a dedicated font and
|
||||
/// sometimes they are not available at all. In the future, this function will
|
||||
/// support selecting a dedicated smallcaps font as well as synthesizing
|
||||
/// smallcaps from normal letters, but this is not yet implemented.
|
||||
///
|
||||
/// # Example
|
||||
/// ```example
|
||||
/// #set par(justify: true)
|
||||
/// #set heading(numbering: "I.")
|
||||
///
|
||||
/// #show heading: it => {
|
||||
/// set block(below: 10pt)
|
||||
/// set text(weight: "regular")
|
||||
/// align(center, smallcaps(it))
|
||||
/// }
|
||||
///
|
||||
/// = Introduction
|
||||
/// #lorem(40)
|
||||
/// ```
|
||||
#[func(title = "Small Capitals")]
|
||||
pub fn smallcaps(
|
||||
/// The text to display to small capitals.
|
||||
body: Content,
|
||||
) -> Content {
|
||||
body.styled(TextElem::set_smallcaps(true))
|
||||
}
|
||||
|
||||
/// Creates blind text.
|
||||
///
|
||||
/// This function yields a Latin-like _Lorem Ipsum_ blind text with the given
|
||||
/// number of words. The sequence of words generated by the function is always
|
||||
/// the same but randomly chosen. As usual for blind texts, it does not make any
|
||||
/// sense. Use it as a placeholder to try layouts.
|
||||
///
|
||||
/// # Example
|
||||
/// ```example
|
||||
/// = Blind Text
|
||||
/// #lorem(30)
|
||||
///
|
||||
/// = More Blind Text
|
||||
/// #lorem(15)
|
||||
/// ```
|
||||
#[func(keywords = ["Blind Text"])]
|
||||
pub fn lorem(
|
||||
/// The length of the blind text in words.
|
||||
words: usize,
|
||||
) -> Str {
|
||||
lipsum::lipsum(words).replace("--", "–").into()
|
||||
}
|
@ -1,547 +0,0 @@
|
||||
use std::f64::consts::SQRT_2;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
/// A rectangle with optional content.
|
||||
///
|
||||
/// # Example
|
||||
/// ```example
|
||||
/// // Without content.
|
||||
/// #rect(width: 35%, height: 30pt)
|
||||
///
|
||||
/// // With content.
|
||||
/// #rect[
|
||||
/// Automatically sized \
|
||||
/// to fit the content.
|
||||
/// ]
|
||||
/// ```
|
||||
#[elem(title = "Rectangle", Layout)]
|
||||
pub struct RectElem {
|
||||
/// The rectangle's width, relative to its parent container.
|
||||
pub width: Smart<Rel<Length>>,
|
||||
|
||||
/// The rectangle's height, relative to its parent container.
|
||||
pub height: Smart<Rel<Length>>,
|
||||
|
||||
/// How to fill the rectangle.
|
||||
///
|
||||
/// When setting a fill, the default stroke disappears. To create a
|
||||
/// rectangle with both fill and stroke, you have to configure both.
|
||||
///
|
||||
/// ```example
|
||||
/// #rect(fill: blue)
|
||||
/// ```
|
||||
pub fill: Option<Paint>,
|
||||
|
||||
/// How to stroke the rectangle. This can be:
|
||||
///
|
||||
/// - `{none}` to disable stroking
|
||||
/// - `{auto}` for a stroke of `{1pt + black}` if and if only if no fill is
|
||||
/// given.
|
||||
/// - Any kind of [stroke]($stroke)
|
||||
/// - A dictionary describing the stroke for each side inidvidually. The
|
||||
/// dictionary can contain the following keys in order of precedence:
|
||||
/// - `top`: The top stroke.
|
||||
/// - `right`: The right stroke.
|
||||
/// - `bottom`: The bottom stroke.
|
||||
/// - `left`: The left stroke.
|
||||
/// - `x`: The horizontal stroke.
|
||||
/// - `y`: The vertical stroke.
|
||||
/// - `rest`: The stroke on all sides except those for which the
|
||||
/// dictionary explicitly sets a size.
|
||||
///
|
||||
/// ```example
|
||||
/// #stack(
|
||||
/// dir: ltr,
|
||||
/// spacing: 1fr,
|
||||
/// rect(stroke: red),
|
||||
/// rect(stroke: 2pt),
|
||||
/// rect(stroke: 2pt + red),
|
||||
/// )
|
||||
/// ```
|
||||
#[resolve]
|
||||
#[fold]
|
||||
pub stroke: Smart<Sides<Option<Option<Stroke>>>>,
|
||||
|
||||
/// How much to round the rectangle's corners, relative to the minimum of
|
||||
/// the width and height divided by two. This can be:
|
||||
///
|
||||
/// - A relative length for a uniform corner radius.
|
||||
/// - A dictionary: With a dictionary, the stroke for each side can be set
|
||||
/// individually. The dictionary can contain the following keys in order
|
||||
/// of precedence:
|
||||
/// - `top-left`: The top-left corner radius.
|
||||
/// - `top-right`: The top-right corner radius.
|
||||
/// - `bottom-right`: The bottom-right corner radius.
|
||||
/// - `bottom-left`: The bottom-left corner radius.
|
||||
/// - `left`: The top-left and bottom-left corner radii.
|
||||
/// - `top`: The top-left and top-right corner radii.
|
||||
/// - `right`: The top-right and bottom-right corner radii.
|
||||
/// - `bottom`: The bottom-left and bottom-right corner radii.
|
||||
/// - `rest`: The radii for all corners except those for which the
|
||||
/// dictionary explicitly sets a size.
|
||||
///
|
||||
/// ```example
|
||||
/// #set rect(stroke: 4pt)
|
||||
/// #rect(
|
||||
/// radius: (
|
||||
/// left: 5pt,
|
||||
/// top-right: 20pt,
|
||||
/// bottom-right: 10pt,
|
||||
/// ),
|
||||
/// stroke: (
|
||||
/// left: red,
|
||||
/// top: yellow,
|
||||
/// right: green,
|
||||
/// bottom: blue,
|
||||
/// ),
|
||||
/// )
|
||||
/// ```
|
||||
#[resolve]
|
||||
#[fold]
|
||||
pub radius: Corners<Option<Rel<Length>>>,
|
||||
|
||||
/// How much to pad the rectangle's content.
|
||||
/// See the [box's documentation]($box.outset) for more details.
|
||||
#[resolve]
|
||||
#[fold]
|
||||
#[default(Sides::splat(Abs::pt(5.0).into()))]
|
||||
pub inset: Sides<Option<Rel<Length>>>,
|
||||
|
||||
/// How much to expand the rectangle's size without affecting the layout.
|
||||
/// See the [box's documentation]($box.outset) for more details.
|
||||
#[resolve]
|
||||
#[fold]
|
||||
pub outset: Sides<Option<Rel<Length>>>,
|
||||
|
||||
/// The content to place into the rectangle.
|
||||
///
|
||||
/// When this is omitted, the rectangle takes on a default size of at most
|
||||
/// `{45pt}` by `{30pt}`.
|
||||
#[positional]
|
||||
pub body: Option<Content>,
|
||||
}
|
||||
|
||||
impl Layout for RectElem {
|
||||
#[tracing::instrument(name = "RectElem::layout", skip_all)]
|
||||
fn layout(
|
||||
&self,
|
||||
vt: &mut Vt,
|
||||
styles: StyleChain,
|
||||
regions: Regions,
|
||||
) -> SourceResult<Fragment> {
|
||||
layout(
|
||||
vt,
|
||||
styles,
|
||||
regions,
|
||||
ShapeKind::Rect,
|
||||
&self.body(styles),
|
||||
Axes::new(self.width(styles), self.height(styles)),
|
||||
self.fill(styles),
|
||||
self.stroke(styles),
|
||||
self.inset(styles),
|
||||
self.outset(styles),
|
||||
self.radius(styles),
|
||||
self.span(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A square with optional content.
|
||||
///
|
||||
/// # Example
|
||||
/// ```example
|
||||
/// // Without content.
|
||||
/// #square(size: 40pt)
|
||||
///
|
||||
/// // With content.
|
||||
/// #square[
|
||||
/// Automatically \
|
||||
/// sized to fit.
|
||||
/// ]
|
||||
/// ```
|
||||
#[elem(Layout)]
|
||||
pub struct SquareElem {
|
||||
/// The square's side length. This is mutually exclusive with `width` and
|
||||
/// `height`.
|
||||
#[external]
|
||||
pub size: Smart<Length>,
|
||||
|
||||
/// The square's width. This is mutually exclusive with `size` and `height`.
|
||||
///
|
||||
/// In contrast to `size`, this can be relative to the parent container's
|
||||
/// width.
|
||||
#[parse(
|
||||
let size = args.named::<Smart<Length>>("size")?.map(|s| s.map(Rel::from));
|
||||
match size {
|
||||
None => args.named("width")?,
|
||||
size => size,
|
||||
}
|
||||
)]
|
||||
pub width: Smart<Rel<Length>>,
|
||||
|
||||
/// The square's height. This is mutually exclusive with `size` and `width`.
|
||||
///
|
||||
/// In contrast to `size`, this can be relative to the parent container's
|
||||
/// height.
|
||||
#[parse(match size {
|
||||
None => args.named("height")?,
|
||||
size => size,
|
||||
})]
|
||||
pub height: Smart<Rel<Length>>,
|
||||
|
||||
/// How to fill the square. See the [rectangle's documentation]($rect.fill)
|
||||
/// for more details.
|
||||
pub fill: Option<Paint>,
|
||||
|
||||
/// How to stroke the square. See the
|
||||
/// [rectangle's documentation]($rect.stroke) for more details.
|
||||
#[resolve]
|
||||
#[fold]
|
||||
pub stroke: Smart<Sides<Option<Option<Stroke>>>>,
|
||||
|
||||
/// How much to round the square's corners. See the
|
||||
/// [rectangle's documentation]($rect.radius) for more details.
|
||||
#[resolve]
|
||||
#[fold]
|
||||
pub radius: Corners<Option<Rel<Length>>>,
|
||||
|
||||
/// How much to pad the square's content. See the
|
||||
/// [box's documentation]($box.inset) for more details.
|
||||
#[resolve]
|
||||
#[fold]
|
||||
#[default(Sides::splat(Abs::pt(5.0).into()))]
|
||||
pub inset: Sides<Option<Rel<Length>>>,
|
||||
|
||||
/// How much to expand the square's size without affecting the layout. See
|
||||
/// the [box's documentation]($box.outset) for more details.
|
||||
#[resolve]
|
||||
#[fold]
|
||||
pub outset: Sides<Option<Rel<Length>>>,
|
||||
|
||||
/// The content to place into the square. The square expands to fit this
|
||||
/// content, keeping the 1-1 aspect ratio.
|
||||
///
|
||||
/// When this is omitted, the square takes on a default size of at most
|
||||
/// `{30pt}`.
|
||||
#[positional]
|
||||
pub body: Option<Content>,
|
||||
}
|
||||
|
||||
impl Layout for SquareElem {
|
||||
#[tracing::instrument(name = "SquareElem::layout", skip_all)]
|
||||
fn layout(
|
||||
&self,
|
||||
vt: &mut Vt,
|
||||
styles: StyleChain,
|
||||
regions: Regions,
|
||||
) -> SourceResult<Fragment> {
|
||||
layout(
|
||||
vt,
|
||||
styles,
|
||||
regions,
|
||||
ShapeKind::Square,
|
||||
&self.body(styles),
|
||||
Axes::new(self.width(styles), self.height(styles)),
|
||||
self.fill(styles),
|
||||
self.stroke(styles),
|
||||
self.inset(styles),
|
||||
self.outset(styles),
|
||||
self.radius(styles),
|
||||
self.span(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// An ellipse with optional content.
|
||||
///
|
||||
/// # Example
|
||||
/// ```example
|
||||
/// // Without content.
|
||||
/// #ellipse(width: 35%, height: 30pt)
|
||||
///
|
||||
/// // With content.
|
||||
/// #ellipse[
|
||||
/// #set align(center)
|
||||
/// Automatically sized \
|
||||
/// to fit the content.
|
||||
/// ]
|
||||
/// ```
|
||||
#[elem(Layout)]
|
||||
pub struct EllipseElem {
|
||||
/// The ellipse's width, relative to its parent container.
|
||||
pub width: Smart<Rel<Length>>,
|
||||
|
||||
/// The ellipse's height, relative to its parent container.
|
||||
pub height: Smart<Rel<Length>>,
|
||||
|
||||
/// How to fill the ellipse. See the [rectangle's documentation]($rect.fill)
|
||||
/// for more details.
|
||||
pub fill: Option<Paint>,
|
||||
|
||||
/// How to stroke the ellipse. See the
|
||||
/// [rectangle's documentation]($rect.stroke) for more details.
|
||||
#[resolve]
|
||||
#[fold]
|
||||
pub stroke: Smart<Option<Stroke>>,
|
||||
|
||||
/// How much to pad the ellipse's content. See the
|
||||
/// [box's documentation]($box.inset) for more details.
|
||||
#[resolve]
|
||||
#[fold]
|
||||
#[default(Sides::splat(Abs::pt(5.0).into()))]
|
||||
pub inset: Sides<Option<Rel<Length>>>,
|
||||
|
||||
/// How much to expand the ellipse's size without affecting the layout. See
|
||||
/// the [box's documentation]($box.outset) for more details.
|
||||
#[resolve]
|
||||
#[fold]
|
||||
pub outset: Sides<Option<Rel<Length>>>,
|
||||
|
||||
/// The content to place into the ellipse.
|
||||
///
|
||||
/// When this is omitted, the ellipse takes on a default size of at most
|
||||
/// `{45pt}` by `{30pt}`.
|
||||
#[positional]
|
||||
pub body: Option<Content>,
|
||||
}
|
||||
|
||||
impl Layout for EllipseElem {
|
||||
#[tracing::instrument(name = "EllipseElem::layout", skip_all)]
|
||||
fn layout(
|
||||
&self,
|
||||
vt: &mut Vt,
|
||||
styles: StyleChain,
|
||||
regions: Regions,
|
||||
) -> SourceResult<Fragment> {
|
||||
layout(
|
||||
vt,
|
||||
styles,
|
||||
regions,
|
||||
ShapeKind::Ellipse,
|
||||
&self.body(styles),
|
||||
Axes::new(self.width(styles), self.height(styles)),
|
||||
self.fill(styles),
|
||||
self.stroke(styles).map(Sides::splat),
|
||||
self.inset(styles),
|
||||
self.outset(styles),
|
||||
Corners::splat(Rel::zero()),
|
||||
self.span(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A circle with optional content.
|
||||
///
|
||||
/// # Example
|
||||
/// ```example
|
||||
/// // Without content.
|
||||
/// #circle(radius: 25pt)
|
||||
///
|
||||
/// // With content.
|
||||
/// #circle[
|
||||
/// #set align(center + horizon)
|
||||
/// Automatically \
|
||||
/// sized to fit.
|
||||
/// ]
|
||||
/// ```
|
||||
#[elem(Layout)]
|
||||
pub struct CircleElem {
|
||||
/// The circle's radius. This is mutually exclusive with `width` and
|
||||
/// `height`.
|
||||
#[external]
|
||||
pub radius: Length,
|
||||
|
||||
/// The circle's width. This is mutually exclusive with `radius` and
|
||||
/// `height`.
|
||||
///
|
||||
/// In contrast to `radius`, this can be relative to the parent container's
|
||||
/// width.
|
||||
#[parse(
|
||||
let size = args
|
||||
.named::<Smart<Length>>("radius")?
|
||||
.map(|s| s.map(|r| 2.0 * Rel::from(r)));
|
||||
match size {
|
||||
None => args.named("width")?,
|
||||
size => size,
|
||||
}
|
||||
)]
|
||||
pub width: Smart<Rel<Length>>,
|
||||
|
||||
/// The circle's height. This is mutually exclusive with `radius` and
|
||||
/// `width`.
|
||||
///
|
||||
/// In contrast to `radius`, this can be relative to the parent container's
|
||||
/// height.
|
||||
#[parse(match size {
|
||||
None => args.named("height")?,
|
||||
size => size,
|
||||
})]
|
||||
pub height: Smart<Rel<Length>>,
|
||||
|
||||
/// How to fill the circle. See the [rectangle's documentation]($rect.fill)
|
||||
/// for more details.
|
||||
pub fill: Option<Paint>,
|
||||
|
||||
/// How to stroke the circle. See the
|
||||
/// [rectangle's documentation]($rect.stroke) for more details.
|
||||
#[resolve]
|
||||
#[fold]
|
||||
#[default(Smart::Auto)]
|
||||
pub stroke: Smart<Option<Stroke>>,
|
||||
|
||||
/// How much to pad the circle's content. See the
|
||||
/// [box's documentation]($box.inset) for more details.
|
||||
#[resolve]
|
||||
#[fold]
|
||||
#[default(Sides::splat(Abs::pt(5.0).into()))]
|
||||
pub inset: Sides<Option<Rel<Length>>>,
|
||||
|
||||
/// How much to expand the circle's size without affecting the layout. See
|
||||
/// the [box's documentation]($box.outset) for more details.
|
||||
#[resolve]
|
||||
#[fold]
|
||||
pub outset: Sides<Option<Rel<Length>>>,
|
||||
|
||||
/// The content to place into the circle. The circle expands to fit this
|
||||
/// content, keeping the 1-1 aspect ratio.
|
||||
#[positional]
|
||||
pub body: Option<Content>,
|
||||
}
|
||||
|
||||
impl Layout for CircleElem {
|
||||
#[tracing::instrument(name = "CircleElem::layout", skip_all)]
|
||||
fn layout(
|
||||
&self,
|
||||
vt: &mut Vt,
|
||||
styles: StyleChain,
|
||||
regions: Regions,
|
||||
) -> SourceResult<Fragment> {
|
||||
layout(
|
||||
vt,
|
||||
styles,
|
||||
regions,
|
||||
ShapeKind::Circle,
|
||||
&self.body(styles),
|
||||
Axes::new(self.width(styles), self.height(styles)),
|
||||
self.fill(styles),
|
||||
self.stroke(styles).map(Sides::splat),
|
||||
self.inset(styles),
|
||||
self.outset(styles),
|
||||
Corners::splat(Rel::zero()),
|
||||
self.span(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Layout a shape.
|
||||
#[tracing::instrument(name = "shape::layout", skip_all)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout(
|
||||
vt: &mut Vt,
|
||||
styles: StyleChain,
|
||||
regions: Regions,
|
||||
kind: ShapeKind,
|
||||
body: &Option<Content>,
|
||||
sizing: Axes<Smart<Rel<Length>>>,
|
||||
fill: Option<Paint>,
|
||||
stroke: Smart<Sides<Option<Stroke<Abs>>>>,
|
||||
mut inset: Sides<Rel<Abs>>,
|
||||
outset: Sides<Rel<Abs>>,
|
||||
radius: Corners<Rel<Abs>>,
|
||||
span: Span,
|
||||
) -> SourceResult<Fragment> {
|
||||
let resolved = sizing
|
||||
.zip_map(regions.base(), |s, r| s.map(|v| v.resolve(styles).relative_to(r)));
|
||||
|
||||
let mut frame;
|
||||
if let Some(child) = body {
|
||||
let region = resolved.unwrap_or(regions.base());
|
||||
if kind.is_round() {
|
||||
inset = inset.map(|side| side + Ratio::new(0.5 - SQRT_2 / 4.0));
|
||||
}
|
||||
|
||||
// Pad the child.
|
||||
let child = child.clone().padded(inset.map(|side| side.map(Length::from)));
|
||||
let expand = sizing.as_ref().map(Smart::is_custom);
|
||||
let pod = Regions::one(region, expand);
|
||||
frame = child.layout(vt, styles, pod)?.into_frame();
|
||||
|
||||
// Enforce correct size.
|
||||
*frame.size_mut() = expand.select(region, frame.size());
|
||||
|
||||
// Relayout with full expansion into square region to make sure
|
||||
// the result is really a square or circle.
|
||||
if kind.is_quadratic() {
|
||||
frame.set_size(Size::splat(frame.size().max_by_side()));
|
||||
let length = frame.size().max_by_side().min(region.min_by_side());
|
||||
let pod = Regions::one(Size::splat(length), Axes::splat(true));
|
||||
frame = child.layout(vt, styles, pod)?.into_frame();
|
||||
}
|
||||
|
||||
// Enforce correct size again.
|
||||
*frame.size_mut() = expand.select(region, frame.size());
|
||||
if kind.is_quadratic() {
|
||||
frame.set_size(Size::splat(frame.size().max_by_side()));
|
||||
}
|
||||
} else {
|
||||
// The default size that a shape takes on if it has no child and
|
||||
// enough space.
|
||||
let default = Size::new(Abs::pt(45.0), Abs::pt(30.0));
|
||||
let mut size = resolved.unwrap_or(default.min(regions.base()));
|
||||
if kind.is_quadratic() {
|
||||
size = Size::splat(size.min_by_side());
|
||||
}
|
||||
frame = Frame::soft(size);
|
||||
}
|
||||
|
||||
// Prepare stroke.
|
||||
let stroke = match stroke {
|
||||
Smart::Auto if fill.is_none() => Sides::splat(Some(FixedStroke::default())),
|
||||
Smart::Auto => Sides::splat(None),
|
||||
Smart::Custom(strokes) => strokes.map(|s| s.map(Stroke::unwrap_or_default)),
|
||||
};
|
||||
|
||||
// Add fill and/or stroke.
|
||||
if fill.is_some() || stroke.iter().any(Option::is_some) {
|
||||
if kind.is_round() {
|
||||
let outset = outset.relative_to(frame.size());
|
||||
let size = frame.size() + outset.sum_by_axis();
|
||||
let pos = Point::new(-outset.left, -outset.top);
|
||||
let shape = ellipse(size, fill, stroke.left);
|
||||
frame.prepend(pos, FrameItem::Shape(shape, span));
|
||||
} else {
|
||||
frame.fill_and_stroke(fill, stroke, outset, radius, span);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply metadata.
|
||||
frame.meta(styles, false);
|
||||
|
||||
Ok(Fragment::frame(frame))
|
||||
}
|
||||
|
||||
/// A category of shape.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum ShapeKind {
|
||||
/// A rectangle with equal side lengths.
|
||||
Square,
|
||||
/// A quadrilateral with four right angles.
|
||||
Rect,
|
||||
/// An ellipse with coinciding foci.
|
||||
Circle,
|
||||
/// A curve around two focal points.
|
||||
Ellipse,
|
||||
}
|
||||
|
||||
impl ShapeKind {
|
||||
/// Whether this shape kind is curvy.
|
||||
fn is_round(self) -> bool {
|
||||
matches!(self, Self::Circle | Self::Ellipse)
|
||||
}
|
||||
|
||||
/// Whether this shape kind has equal side length.
|
||||
fn is_quadratic(self) -> bool {
|
||||
matches!(self, Self::Square | Self::Circle)
|
||||
}
|
||||
}
|
@ -1,4 +1,11 @@
|
||||
use super::*;
|
||||
use heck::ToKebabCase;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::{DeriveInput, Ident, Result, Token};
|
||||
|
||||
use crate::util::{documentation, foundations};
|
||||
|
||||
/// Expand the `#[derive(Cast)]` macro.
|
||||
pub fn derive_cast(item: DeriveInput) -> Result<TokenStream> {
|
||||
@ -43,9 +50,9 @@ pub fn derive_cast(item: DeriveInput) -> Result<TokenStream> {
|
||||
});
|
||||
|
||||
Ok(quote! {
|
||||
::typst::eval::cast! {
|
||||
#foundations::cast! {
|
||||
#ty,
|
||||
self => ::typst::eval::IntoValue::into_value(match self {
|
||||
self => #foundations::IntoValue::into_value(match self {
|
||||
#(#variants_to_strs),*
|
||||
}),
|
||||
#(#strs_to_variants),*
|
||||
@ -62,8 +69,6 @@ struct Variant {
|
||||
|
||||
/// Expand the `cast!` macro.
|
||||
pub fn cast(stream: TokenStream) -> Result<TokenStream> {
|
||||
let eval = quote! { ::typst::eval };
|
||||
|
||||
let input: CastInput = syn::parse2(stream)?;
|
||||
let ty = &input.ty;
|
||||
let castable_body = create_castable_body(&input);
|
||||
@ -74,16 +79,16 @@ pub fn cast(stream: TokenStream) -> Result<TokenStream> {
|
||||
|
||||
let reflect = (!input.from_value.is_empty() || input.dynamic).then(|| {
|
||||
quote! {
|
||||
impl #eval::Reflect for #ty {
|
||||
fn input() -> #eval::CastInfo {
|
||||
impl #foundations::Reflect for #ty {
|
||||
fn input() -> #foundations::CastInfo {
|
||||
#input_body
|
||||
}
|
||||
|
||||
fn output() -> #eval::CastInfo {
|
||||
fn output() -> #foundations::CastInfo {
|
||||
#output_body
|
||||
}
|
||||
|
||||
fn castable(value: &#eval::Value) -> bool {
|
||||
fn castable(value: &#foundations::Value) -> bool {
|
||||
#castable_body
|
||||
}
|
||||
}
|
||||
@ -92,8 +97,8 @@ pub fn cast(stream: TokenStream) -> Result<TokenStream> {
|
||||
|
||||
let into_value = (input.into_value.is_some() || input.dynamic).then(|| {
|
||||
quote! {
|
||||
impl #eval::IntoValue for #ty {
|
||||
fn into_value(self) -> #eval::Value {
|
||||
impl #foundations::IntoValue for #ty {
|
||||
fn into_value(self) -> #foundations::Value {
|
||||
#into_value_body
|
||||
}
|
||||
}
|
||||
@ -102,8 +107,8 @@ pub fn cast(stream: TokenStream) -> Result<TokenStream> {
|
||||
|
||||
let from_value = (!input.from_value.is_empty() || input.dynamic).then(|| {
|
||||
quote! {
|
||||
impl #eval::FromValue for #ty {
|
||||
fn from_value(value: #eval::Value) -> ::typst::diag::StrResult<Self> {
|
||||
impl #foundations::FromValue for #ty {
|
||||
fn from_value(value: #foundations::Value) -> ::typst::diag::StrResult<Self> {
|
||||
#from_value_body
|
||||
}
|
||||
}
|
||||
@ -196,7 +201,7 @@ fn create_castable_body(input: &CastInput) -> TokenStream {
|
||||
}
|
||||
Pattern::Ty(_, ty) => {
|
||||
casts.push(quote! {
|
||||
if <#ty as ::typst::eval::Reflect>::castable(value) {
|
||||
if <#ty as #foundations::Reflect>::castable(value) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@ -206,7 +211,7 @@ fn create_castable_body(input: &CastInput) -> TokenStream {
|
||||
|
||||
let dynamic_check = input.dynamic.then(|| {
|
||||
quote! {
|
||||
if let ::typst::eval::Value::Dyn(dynamic) = &value {
|
||||
if let #foundations::Value::Dyn(dynamic) = &value {
|
||||
if dynamic.is::<Self>() {
|
||||
return true;
|
||||
}
|
||||
@ -216,7 +221,7 @@ fn create_castable_body(input: &CastInput) -> TokenStream {
|
||||
|
||||
let str_check = (!strings.is_empty()).then(|| {
|
||||
quote! {
|
||||
if let ::typst::eval::Value::Str(string) = &value {
|
||||
if let #foundations::Value::Str(string) = &value {
|
||||
match string.as_str() {
|
||||
#(#strings,)*
|
||||
_ => {}
|
||||
@ -241,21 +246,21 @@ fn create_input_body(input: &CastInput) -> TokenStream {
|
||||
infos.push(match &cast.pattern {
|
||||
Pattern::Str(lit) => {
|
||||
quote! {
|
||||
::typst::eval::CastInfo::Value(
|
||||
::typst::eval::IntoValue::into_value(#lit),
|
||||
#foundations::CastInfo::Value(
|
||||
#foundations::IntoValue::into_value(#lit),
|
||||
#docs,
|
||||
)
|
||||
}
|
||||
}
|
||||
Pattern::Ty(_, ty) => {
|
||||
quote! { <#ty as ::typst::eval::Reflect>::input() }
|
||||
quote! { <#ty as #foundations::Reflect>::input() }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if input.dynamic {
|
||||
infos.push(quote! {
|
||||
::typst::eval::CastInfo::Type(::typst::eval::Type::of::<Self>())
|
||||
#foundations::CastInfo::Type(#foundations::Type::of::<Self>())
|
||||
});
|
||||
}
|
||||
|
||||
@ -266,7 +271,7 @@ fn create_input_body(input: &CastInput) -> TokenStream {
|
||||
|
||||
fn create_output_body(input: &CastInput) -> TokenStream {
|
||||
if input.dynamic {
|
||||
quote! { ::typst::eval::CastInfo::Type(::typst::eval::Type::of::<Self>()) }
|
||||
quote! { #foundations::CastInfo::Type(#foundations::Type::of::<Self>()) }
|
||||
} else {
|
||||
quote! { Self::input() }
|
||||
}
|
||||
@ -276,7 +281,7 @@ fn create_into_value_body(input: &CastInput) -> TokenStream {
|
||||
if let Some(expr) = &input.into_value {
|
||||
quote! { #expr }
|
||||
} else {
|
||||
quote! { ::typst::eval::Value::dynamic(self) }
|
||||
quote! { #foundations::Value::dynamic(self) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -292,8 +297,8 @@ fn create_from_value_body(input: &CastInput) -> TokenStream {
|
||||
}
|
||||
Pattern::Ty(binding, ty) => {
|
||||
cast_checks.push(quote! {
|
||||
if <#ty as ::typst::eval::Reflect>::castable(&value) {
|
||||
let #binding = <#ty as ::typst::eval::FromValue>::from_value(value)?;
|
||||
if <#ty as #foundations::Reflect>::castable(&value) {
|
||||
let #binding = <#ty as #foundations::FromValue>::from_value(value)?;
|
||||
return Ok(#expr);
|
||||
}
|
||||
});
|
||||
@ -303,7 +308,7 @@ fn create_from_value_body(input: &CastInput) -> TokenStream {
|
||||
|
||||
let dynamic_check = input.dynamic.then(|| {
|
||||
quote! {
|
||||
if let ::typst::eval::Value::Dyn(dynamic) = &value {
|
||||
if let #foundations::Value::Dyn(dynamic) = &value {
|
||||
if let Some(concrete) = dynamic.downcast::<Self>() {
|
||||
return Ok(concrete.clone());
|
||||
}
|
||||
@ -313,7 +318,7 @@ fn create_from_value_body(input: &CastInput) -> TokenStream {
|
||||
|
||||
let str_check = (!string_arms.is_empty()).then(|| {
|
||||
quote! {
|
||||
if let ::typst::eval::Value::Str(string) = &value {
|
||||
if let #foundations::Value::Str(string) = &value {
|
||||
match string.as_str() {
|
||||
#(#string_arms,)*
|
||||
_ => {}
|
||||
@ -326,6 +331,6 @@ fn create_from_value_body(input: &CastInput) -> TokenStream {
|
||||
#dynamic_check
|
||||
#str_check
|
||||
#(#cast_checks)*
|
||||
Err(<Self as ::typst::eval::Reflect>::error(&value))
|
||||
Err(<Self as #foundations::Reflect>::error(&value))
|
||||
}
|
||||
}
|
||||
|
57
crates/typst-macros/src/category.rs
Normal file
57
crates/typst-macros/src/category.rs
Normal file
@ -0,0 +1,57 @@
|
||||
use heck::{ToKebabCase, ToTitleCase};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::{Attribute, Ident, Result, Token, Type, Visibility};
|
||||
|
||||
use crate::util::{documentation, foundations};
|
||||
|
||||
/// Expand the `#[category]` macro.
|
||||
pub fn category(_: TokenStream, item: syn::Item) -> Result<TokenStream> {
|
||||
let syn::Item::Verbatim(stream) = item else {
|
||||
bail!(item, "expected bare static");
|
||||
};
|
||||
|
||||
let BareStatic { attrs, vis, ident, ty, .. } = syn::parse2(stream)?;
|
||||
|
||||
let name = ident.to_string().to_kebab_case();
|
||||
let title = name.to_title_case();
|
||||
let docs = documentation(&attrs);
|
||||
|
||||
Ok(quote! {
|
||||
#(#attrs)*
|
||||
#vis static #ident: #ty = {
|
||||
static DATA: #foundations::CategoryData = #foundations::CategoryData {
|
||||
name: #name,
|
||||
title: #title,
|
||||
docs: #docs,
|
||||
};
|
||||
#foundations::Category::from_data(&DATA)
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse a bare `pub static CATEGORY: Category;` item.
|
||||
pub struct BareStatic {
|
||||
pub attrs: Vec<Attribute>,
|
||||
pub vis: Visibility,
|
||||
pub static_token: Token![static],
|
||||
pub ident: Ident,
|
||||
pub colon_token: Token![:],
|
||||
pub ty: Type,
|
||||
pub semi_token: Token![;],
|
||||
}
|
||||
|
||||
impl Parse for BareStatic {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
Ok(Self {
|
||||
attrs: input.call(Attribute::parse_outer)?,
|
||||
vis: input.parse()?,
|
||||
static_token: input.parse()?,
|
||||
ident: input.parse()?,
|
||||
colon_token: input.parse()?,
|
||||
ty: input.parse()?,
|
||||
semi_token: input.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
@ -1,4 +1,15 @@
|
||||
use super::*;
|
||||
use heck::{ToKebabCase, ToShoutySnakeCase, ToUpperCamelCase};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::{parse_quote, Ident, Result, Token};
|
||||
|
||||
use crate::util::{
|
||||
determine_name_and_title, documentation, foundations, has_attr, kw, parse_attr,
|
||||
parse_flag, parse_string, parse_string_array, quote_option, validate_attrs,
|
||||
BlockWithReturn,
|
||||
};
|
||||
|
||||
/// Expand the `#[elem]` macro.
|
||||
pub fn elem(stream: TokenStream, body: syn::ItemStruct) -> Result<TokenStream> {
|
||||
@ -136,7 +147,6 @@ struct Field {
|
||||
synthesized: bool,
|
||||
borrowed: bool,
|
||||
ghost: bool,
|
||||
forced_variant: Option<usize>,
|
||||
parse: Option<BlockWithReturn>,
|
||||
default: Option<syn::Expr>,
|
||||
}
|
||||
@ -226,10 +236,6 @@ fn parse_field(field: &syn::Field) -> Result<Field> {
|
||||
docs: documentation(&attrs),
|
||||
internal: has_attr(&mut attrs, "internal"),
|
||||
external: has_attr(&mut attrs, "external"),
|
||||
forced_variant: parse_attr::<syn::LitInt>(&mut attrs, "variant")?
|
||||
.flatten()
|
||||
.map(|lit| lit.base10_parse())
|
||||
.transpose()?,
|
||||
positional,
|
||||
required,
|
||||
variadic,
|
||||
@ -262,12 +268,12 @@ fn parse_field(field: &syn::Field) -> Result<Field> {
|
||||
|
||||
if field.resolve {
|
||||
let output = &field.output;
|
||||
field.output = parse_quote! { <#output as ::typst::model::Resolve>::Output };
|
||||
field.output = parse_quote! { <#output as #foundations::Resolve>::Output };
|
||||
}
|
||||
|
||||
if field.fold {
|
||||
let output = &field.output;
|
||||
field.output = parse_quote! { <#output as ::typst::model::Fold>::Output };
|
||||
field.output = parse_quote! { <#output as #foundations::Fold>::Output };
|
||||
}
|
||||
|
||||
validate_attrs(&attrs)?;
|
||||
@ -317,8 +323,8 @@ fn create(element: &Elem) -> Result<TokenStream> {
|
||||
|
||||
let label_and_location = element.unless_capability("Unlabellable", || {
|
||||
quote! {
|
||||
location: Option<::typst::model::Location>,
|
||||
label: Option<::typst::model::Label>,
|
||||
location: Option<::typst::introspection::Location>,
|
||||
label: Option<#foundations::Label>,
|
||||
prepared: bool,
|
||||
}
|
||||
});
|
||||
@ -330,7 +336,7 @@ fn create(element: &Elem) -> Result<TokenStream> {
|
||||
#vis struct #ident {
|
||||
span: ::typst::syntax::Span,
|
||||
#label_and_location
|
||||
guards: ::std::vec::Vec<::typst::model::Guard>,
|
||||
guards: ::std::vec::Vec<#foundations::Guard>,
|
||||
|
||||
#(#fields,)*
|
||||
}
|
||||
@ -353,9 +359,9 @@ fn create(element: &Elem) -> Result<TokenStream> {
|
||||
#partial_eq_impl
|
||||
#repr_impl
|
||||
|
||||
impl ::typst::eval::IntoValue for #ident {
|
||||
fn into_value(self) -> ::typst::eval::Value {
|
||||
::typst::eval::Value::Content(::typst::model::Content::new(self))
|
||||
impl #foundations::IntoValue for #ident {
|
||||
fn into_value(self) -> #foundations::Value {
|
||||
#foundations::Value::Content(#foundations::Content::new(self))
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -382,12 +388,9 @@ fn create_field(field: &Field) -> TokenStream {
|
||||
|
||||
/// Creates the element's enum for field identifiers.
|
||||
fn create_fields_enum(element: &Elem) -> TokenStream {
|
||||
let model = quote! { ::typst::model };
|
||||
let Elem { ident, enum_ident, .. } = element;
|
||||
|
||||
let mut fields = element.real_fields().collect::<Vec<_>>();
|
||||
fields.sort_by_key(|field| field.forced_variant.unwrap_or(usize::MAX));
|
||||
|
||||
let fields = element.real_fields().collect::<Vec<_>>();
|
||||
let field_names = fields.iter().map(|Field { name, .. }| name).collect::<Vec<_>>();
|
||||
let field_consts = fields
|
||||
.iter()
|
||||
@ -399,20 +402,14 @@ fn create_fields_enum(element: &Elem) -> TokenStream {
|
||||
.map(|Field { enum_ident, .. }| enum_ident)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let definitions =
|
||||
fields.iter().map(|Field { forced_variant, enum_ident, .. }| {
|
||||
if let Some(variant) = forced_variant {
|
||||
let variant = proc_macro2::Literal::u8_unsuffixed(*variant as _);
|
||||
quote! { #enum_ident = #variant }
|
||||
} else {
|
||||
quote! { #enum_ident }
|
||||
}
|
||||
});
|
||||
let definitions = fields.iter().map(|Field { enum_ident, .. }| {
|
||||
quote! { #enum_ident }
|
||||
});
|
||||
|
||||
quote! {
|
||||
// To hide the private type
|
||||
const _: () = {
|
||||
impl #model::ElementFields for #ident {
|
||||
impl #foundations::ElementFields for #ident {
|
||||
type Fields = #enum_ident;
|
||||
}
|
||||
|
||||
@ -578,16 +575,15 @@ fn create_push_field_method(field: &Field) -> TokenStream {
|
||||
|
||||
/// Create a setter method for a field.
|
||||
fn create_set_field_method(element: &Elem, field: &Field) -> TokenStream {
|
||||
let model = quote! { ::typst::model };
|
||||
let elem = &element.ident;
|
||||
let Field { vis, ident, set_ident, enum_ident, ty, name, .. } = field;
|
||||
let doc = format!("Create a style property for the `{}` field.", name);
|
||||
quote! {
|
||||
#[doc = #doc]
|
||||
#vis fn #set_ident(#ident: #ty) -> ::typst::model::Style {
|
||||
::typst::model::Style::Property(::typst::model::Property::new(
|
||||
<Self as ::typst::model::NativeElement>::elem(),
|
||||
<#elem as #model::ElementFields>::Fields::#enum_ident as u8,
|
||||
#vis fn #set_ident(#ident: #ty) -> #foundations::Style {
|
||||
#foundations::Style::Property(#foundations::Property::new(
|
||||
<Self as #foundations::NativeElement>::elem(),
|
||||
<#elem as #foundations::ElementFields>::Fields::#enum_ident as u8,
|
||||
#ident,
|
||||
))
|
||||
}
|
||||
@ -608,7 +604,7 @@ fn create_field_in_method(element: &Elem, field: &Field) -> TokenStream {
|
||||
|
||||
quote! {
|
||||
#[doc = #doc]
|
||||
#vis fn #ident_in(styles: ::typst::model::StyleChain) -> #output {
|
||||
#vis fn #ident_in(styles: #foundations::StyleChain) -> #output {
|
||||
#access
|
||||
}
|
||||
}
|
||||
@ -646,7 +642,7 @@ fn create_field_method(element: &Elem, field: &Field) -> TokenStream {
|
||||
|
||||
quote! {
|
||||
#[doc = #docs]
|
||||
#vis fn #ident<'a>(&'a self, styles: ::typst::model::StyleChain<'a>) -> &'a #output {
|
||||
#vis fn #ident<'a>(&'a self, styles: #foundations::StyleChain<'a>) -> &'a #output {
|
||||
#access
|
||||
}
|
||||
}
|
||||
@ -655,7 +651,7 @@ fn create_field_method(element: &Elem, field: &Field) -> TokenStream {
|
||||
|
||||
quote! {
|
||||
#[doc = #docs]
|
||||
#vis fn #ident(&self, styles: ::typst::model::StyleChain) -> #output {
|
||||
#vis fn #ident(&self, styles: #foundations::StyleChain) -> #output {
|
||||
#access
|
||||
}
|
||||
}
|
||||
@ -668,7 +664,6 @@ fn create_style_chain_access(
|
||||
field: &Field,
|
||||
inherent: TokenStream,
|
||||
) -> TokenStream {
|
||||
let model = quote! { ::typst::model };
|
||||
let elem = &element.ident;
|
||||
|
||||
let Field { ty, default, enum_ident, .. } = field;
|
||||
@ -693,8 +688,8 @@ fn create_style_chain_access(
|
||||
quote! {
|
||||
#init
|
||||
styles.#getter::<#ty>(
|
||||
<Self as ::typst::model::NativeElement>::elem(),
|
||||
<#elem as #model::ElementFields>::Fields::#enum_ident as u8,
|
||||
<Self as #foundations::NativeElement>::elem(),
|
||||
<#elem as #foundations::ElementFields>::Fields::#enum_ident as u8,
|
||||
#inherent,
|
||||
#default,
|
||||
)
|
||||
@ -703,9 +698,6 @@ fn create_style_chain_access(
|
||||
|
||||
/// Creates the element's `Pack` implementation.
|
||||
fn create_native_elem_impl(element: &Elem) -> TokenStream {
|
||||
let eval = quote! { ::typst::eval };
|
||||
let model = quote! { ::typst::model };
|
||||
|
||||
let Elem { name, ident, title, scope, keywords, docs, .. } = element;
|
||||
|
||||
let vtable_func = create_vtable_func(element);
|
||||
@ -716,9 +708,9 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream {
|
||||
.map(create_param_info);
|
||||
|
||||
let scope = if *scope {
|
||||
quote! { <#ident as #eval::NativeScope>::scope() }
|
||||
quote! { <#ident as #foundations::NativeScope>::scope() }
|
||||
} else {
|
||||
quote! { #eval::Scope::new() }
|
||||
quote! { #foundations::Scope::new() }
|
||||
};
|
||||
|
||||
// Fields that can be accessed using the `field` method.
|
||||
@ -729,37 +721,39 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream {
|
||||
|
||||
if field.ghost {
|
||||
quote! {
|
||||
<#elem as #model::ElementFields>::Fields::#name => None,
|
||||
<#elem as #foundations::ElementFields>::Fields::#name => None,
|
||||
}
|
||||
} else if field.inherent() {
|
||||
quote! {
|
||||
<#elem as #model::ElementFields>::Fields::#name => Some(
|
||||
::typst::eval::IntoValue::into_value(self.#field_ident.clone())
|
||||
<#elem as #foundations::ElementFields>::Fields::#name => Some(
|
||||
#foundations::IntoValue::into_value(self.#field_ident.clone())
|
||||
),
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
<#elem as #model::ElementFields>::Fields::#name => {
|
||||
self.#field_ident.clone().map(::typst::eval::IntoValue::into_value)
|
||||
<#elem as #foundations::ElementFields>::Fields::#name => {
|
||||
self.#field_ident.clone().map(#foundations::IntoValue::into_value)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Fields that can be set using the `set_field` method.
|
||||
let field_set_matches = element.visible_fields()
|
||||
.filter(|field| field.settable() && !field.synthesized && !field.ghost).map(|field| {
|
||||
let elem = &element.ident;
|
||||
let name = &field.enum_ident;
|
||||
let field_ident = &field.ident;
|
||||
let field_set_matches = element
|
||||
.visible_fields()
|
||||
.filter(|field| field.settable() && !field.synthesized && !field.ghost)
|
||||
.map(|field| {
|
||||
let elem = &element.ident;
|
||||
let name = &field.enum_ident;
|
||||
let field_ident = &field.ident;
|
||||
|
||||
quote! {
|
||||
<#elem as #model::ElementFields>::Fields::#name => {
|
||||
self.#field_ident = Some(::typst::eval::FromValue::from_value(value)?);
|
||||
return Ok(());
|
||||
quote! {
|
||||
<#elem as #foundations::ElementFields>::Fields::#name => {
|
||||
self.#field_ident = Some(#foundations::FromValue::from_value(value)?);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Fields that are inherent.
|
||||
let field_inherent_matches = element
|
||||
@ -771,8 +765,8 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream {
|
||||
let field_ident = &field.ident;
|
||||
|
||||
quote! {
|
||||
<#elem as #model::ElementFields>::Fields::#name => {
|
||||
self.#field_ident = ::typst::eval::FromValue::from_value(value)?;
|
||||
<#elem as #foundations::ElementFields>::Fields::#name => {
|
||||
self.#field_ident = #foundations::FromValue::from_value(value)?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
@ -790,13 +784,13 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream {
|
||||
// Internal fields create an error that they are unknown.
|
||||
let unknown_field = format!("unknown field `{field_name}` on `{name}`");
|
||||
quote! {
|
||||
<#elem as #model::ElementFields>::Fields::#ident => ::typst::diag::bail!(#unknown_field),
|
||||
<#elem as #foundations::ElementFields>::Fields::#ident => ::typst::diag::bail!(#unknown_field),
|
||||
}
|
||||
} else {
|
||||
// Fields that cannot be set create an error that they are not settable.
|
||||
let not_settable = format!("cannot set `{field_name}` on `{name}`");
|
||||
quote! {
|
||||
<#elem as #model::ElementFields>::Fields::#ident => ::typst::diag::bail!(#not_settable),
|
||||
<#elem as #foundations::ElementFields>::Fields::#ident => ::typst::diag::bail!(#not_settable),
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -824,15 +818,15 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream {
|
||||
let field_ident = &field.ident;
|
||||
|
||||
let field_call = if name.len() > 15 {
|
||||
quote! { EcoString::from(#name).into() }
|
||||
quote! { ::ecow::EcoString::from(#name).into() }
|
||||
} else {
|
||||
quote! { EcoString::inline(#name).into() }
|
||||
quote! { ::ecow::EcoString::inline(#name).into() }
|
||||
};
|
||||
|
||||
quote! {
|
||||
fields.insert(
|
||||
#field_call,
|
||||
::typst::eval::IntoValue::into_value(self.#field_ident.clone())
|
||||
#foundations::IntoValue::into_value(self.#field_ident.clone())
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -847,16 +841,16 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream {
|
||||
let field_ident = &field.ident;
|
||||
|
||||
let field_call = if name.len() > 15 {
|
||||
quote! { EcoString::from(#name).into() }
|
||||
quote! { ::ecow::EcoString::from(#name).into() }
|
||||
} else {
|
||||
quote! { EcoString::inline(#name).into() }
|
||||
quote! { ::ecow::EcoString::inline(#name).into() }
|
||||
};
|
||||
|
||||
quote! {
|
||||
if let Some(value) = &self.#field_ident {
|
||||
fields.insert(
|
||||
#field_call,
|
||||
::typst::eval::IntoValue::into_value(value.clone())
|
||||
#foundations::IntoValue::into_value(value.clone())
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -889,7 +883,7 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream {
|
||||
let label_field = element
|
||||
.unless_capability("Unlabellable", || {
|
||||
quote! {
|
||||
self.label().map(::typst::eval::Value::Label)
|
||||
self.label().map(#foundations::Value::Label)
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| quote! { None });
|
||||
@ -905,51 +899,51 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream {
|
||||
let local_name = element
|
||||
.if_capability(
|
||||
"LocalName",
|
||||
|| quote! { Some(<#ident as ::typst::model::LocalName>::local_name) },
|
||||
|| quote! { Some(<#ident as ::typst::text::LocalName>::local_name) },
|
||||
)
|
||||
.unwrap_or_else(|| quote! { None });
|
||||
|
||||
let unknown_field = format!("unknown field {{}} on {}", name);
|
||||
let label_error = format!("cannot set label on {}", name);
|
||||
let data = quote! {
|
||||
#model::NativeElementData {
|
||||
#foundations::NativeElementData {
|
||||
name: #name,
|
||||
title: #title,
|
||||
docs: #docs,
|
||||
keywords: &[#(#keywords),*],
|
||||
construct: <#ident as #model::Construct>::construct,
|
||||
set: <#ident as #model::Set>::set,
|
||||
construct: <#ident as #foundations::Construct>::construct,
|
||||
set: <#ident as #foundations::Set>::set,
|
||||
vtable: #vtable_func,
|
||||
field_id: |name|
|
||||
<
|
||||
<#ident as #model::ElementFields>::Fields as ::std::str::FromStr
|
||||
<#ident as #foundations::ElementFields>::Fields as ::std::str::FromStr
|
||||
>::from_str(name).ok().map(|id| id as u8),
|
||||
field_name: |id|
|
||||
<
|
||||
<#ident as #model::ElementFields>::Fields as ::std::convert::TryFrom<u8>
|
||||
>::try_from(id).ok().map(<#ident as #model::ElementFields>::Fields::to_str),
|
||||
<#ident as #foundations::ElementFields>::Fields as ::std::convert::TryFrom<u8>
|
||||
>::try_from(id).ok().map(<#ident as #foundations::ElementFields>::Fields::to_str),
|
||||
local_name: #local_name,
|
||||
scope: #eval::Lazy::new(|| #scope),
|
||||
params: #eval::Lazy::new(|| ::std::vec![#(#params),*])
|
||||
scope: #foundations::Lazy::new(|| #scope),
|
||||
params: #foundations::Lazy::new(|| ::std::vec![#(#params),*])
|
||||
}
|
||||
};
|
||||
|
||||
quote! {
|
||||
impl #model::NativeElement for #ident {
|
||||
fn data() -> &'static #model::NativeElementData {
|
||||
static DATA: #model::NativeElementData = #data;
|
||||
impl #foundations::NativeElement for #ident {
|
||||
fn data() -> &'static #foundations::NativeElementData {
|
||||
static DATA: #foundations::NativeElementData = #data;
|
||||
&DATA
|
||||
}
|
||||
|
||||
fn dyn_elem(&self) -> #model::Element {
|
||||
#model::Element::of::<Self>()
|
||||
fn dyn_elem(&self) -> #foundations::Element {
|
||||
#foundations::Element::of::<Self>()
|
||||
}
|
||||
|
||||
fn dyn_hash(&self, mut hasher: &mut dyn ::std::hash::Hasher) {
|
||||
<Self as ::std::hash::Hash>::hash(self, &mut hasher);
|
||||
}
|
||||
|
||||
fn dyn_eq(&self, other: &#model::Content) -> bool {
|
||||
fn dyn_eq(&self, other: &#foundations::Content) -> bool {
|
||||
if let Some(other) = other.to::<Self>() {
|
||||
<Self as ::std::cmp::PartialEq>::eq(self, other)
|
||||
} else {
|
||||
@ -957,7 +951,7 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream {
|
||||
}
|
||||
}
|
||||
|
||||
fn dyn_clone(&self) -> ::std::sync::Arc<dyn #model::NativeElement> {
|
||||
fn dyn_clone(&self) -> ::std::sync::Arc<dyn #foundations::NativeElement> {
|
||||
::std::sync::Arc::new(Clone::clone(self))
|
||||
}
|
||||
|
||||
@ -983,27 +977,27 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream {
|
||||
}
|
||||
}
|
||||
|
||||
fn label(&self) -> Option<#model::Label> {
|
||||
fn label(&self) -> Option<#foundations::Label> {
|
||||
#label
|
||||
}
|
||||
|
||||
fn set_label(&mut self, label: #model::Label) {
|
||||
fn set_label(&mut self, label: #foundations::Label) {
|
||||
#set_label
|
||||
}
|
||||
|
||||
fn location(&self) -> Option<#model::Location> {
|
||||
fn location(&self) -> Option<::typst::introspection::Location> {
|
||||
#location
|
||||
}
|
||||
|
||||
fn set_location(&mut self, location: #model::Location) {
|
||||
fn set_location(&mut self, location: ::typst::introspection::Location) {
|
||||
#set_location
|
||||
}
|
||||
|
||||
fn push_guard(&mut self, guard: #model::Guard) {
|
||||
fn push_guard(&mut self, guard: #foundations::Guard) {
|
||||
self.guards.push(guard);
|
||||
}
|
||||
|
||||
fn is_guarded(&self, guard: #model::Guard) -> bool {
|
||||
fn is_guarded(&self, guard: #foundations::Guard) -> bool {
|
||||
self.guards.contains(&guard)
|
||||
}
|
||||
|
||||
@ -1023,30 +1017,30 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream {
|
||||
#prepared
|
||||
}
|
||||
|
||||
fn field(&self, id: u8) -> Option<::typst::eval::Value> {
|
||||
let id = <#ident as #model::ElementFields>::Fields::try_from(id).ok()?;
|
||||
fn field(&self, id: u8) -> Option<#foundations::Value> {
|
||||
let id = <#ident as #foundations::ElementFields>::Fields::try_from(id).ok()?;
|
||||
match id {
|
||||
<#ident as #model::ElementFields>::Fields::Label => #label_field,
|
||||
<#ident as #foundations::ElementFields>::Fields::Label => #label_field,
|
||||
#(#field_matches)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn fields(&self) -> Dict {
|
||||
let mut fields = Dict::new();
|
||||
fn fields(&self) -> #foundations::Dict {
|
||||
let mut fields = #foundations::Dict::new();
|
||||
#(#field_dict)*
|
||||
#(#field_opt_dict)*
|
||||
fields
|
||||
}
|
||||
|
||||
fn set_field(&mut self, id: u8, value: Value) -> ::typst::diag::StrResult<()> {
|
||||
let id = <#ident as #model::ElementFields>::Fields::try_from(id)
|
||||
fn set_field(&mut self, id: u8, value: #foundations::Value) -> ::typst::diag::StrResult<()> {
|
||||
let id = <#ident as #foundations::ElementFields>::Fields::try_from(id)
|
||||
.map_err(|_| ::ecow::eco_format!(#unknown_field, id))?;
|
||||
match id {
|
||||
#(#field_set_matches)*
|
||||
#(#field_inherent_matches)*
|
||||
#(#field_not_set_matches)*
|
||||
<#ident as #model::ElementFields>::Fields::Label => {
|
||||
<#ident as #foundations::ElementFields>::Fields::Label => {
|
||||
::typst::diag::bail!(#label_error);
|
||||
}
|
||||
}
|
||||
@ -1087,18 +1081,18 @@ fn create_construct_impl(element: &Elem) -> TokenStream {
|
||||
.map(|field| &field.ident);
|
||||
|
||||
quote! {
|
||||
impl ::typst::model::Construct for #ident {
|
||||
impl #foundations::Construct for #ident {
|
||||
fn construct(
|
||||
vm: &mut ::typst::eval::Vm,
|
||||
args: &mut ::typst::eval::Args,
|
||||
) -> ::typst::diag::SourceResult<::typst::model::Content> {
|
||||
args: &mut #foundations::Args,
|
||||
) -> ::typst::diag::SourceResult<#foundations::Content> {
|
||||
#(#pre)*
|
||||
|
||||
let mut element = Self::new(#(#defaults),*);
|
||||
|
||||
#(#handlers)*
|
||||
|
||||
Ok(::typst::model::Content::new(element))
|
||||
Ok(#foundations::Content::new(element))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1119,12 +1113,12 @@ fn create_set_impl(element: &Elem) -> TokenStream {
|
||||
});
|
||||
|
||||
quote! {
|
||||
impl ::typst::model::Set for #ident {
|
||||
impl #foundations::Set for #ident {
|
||||
fn set(
|
||||
vm: &mut Vm,
|
||||
args: &mut ::typst::eval::Args,
|
||||
) -> ::typst::diag::SourceResult<::typst::model::Styles> {
|
||||
let mut styles = ::typst::model::Styles::new();
|
||||
vm: &mut ::typst::eval::Vm,
|
||||
args: &mut #foundations::Args,
|
||||
) -> ::typst::diag::SourceResult<#foundations::Styles> {
|
||||
let mut styles = #foundations::Styles::new();
|
||||
#(#handlers)*
|
||||
Ok(styles)
|
||||
}
|
||||
@ -1135,7 +1129,7 @@ fn create_set_impl(element: &Elem) -> TokenStream {
|
||||
/// Creates the element's `Locatable` implementation.
|
||||
fn create_locatable_impl(element: &Elem) -> TokenStream {
|
||||
let ident = &element.ident;
|
||||
quote! { impl ::typst::model::Locatable for #ident {} }
|
||||
quote! { impl ::typst::introspection::Locatable for #ident {} }
|
||||
}
|
||||
|
||||
/// Creates the element's `PartialEq` implementation.
|
||||
@ -1159,12 +1153,12 @@ fn create_repr_impl(element: &Elem) -> TokenStream {
|
||||
let ident = &element.ident;
|
||||
let repr_format = format!("{}{{}}", element.name);
|
||||
quote! {
|
||||
impl ::typst::eval::Repr for #ident {
|
||||
impl #foundations::Repr for #ident {
|
||||
fn repr(&self) -> ::ecow::EcoString {
|
||||
let fields = self.fields().into_iter()
|
||||
.map(|(name, value)| eco_format!("{}: {}", name, value.repr()))
|
||||
let fields = #foundations::NativeElement::fields(self).into_iter()
|
||||
.map(|(name, value)| ::ecow::eco_format!("{}: {}", name, value.repr()))
|
||||
.collect::<Vec<_>>();
|
||||
::ecow::eco_format!(#repr_format, ::typst::eval::repr::pretty_array_like(&fields, false))
|
||||
::ecow::eco_format!(#repr_format, #foundations::repr::pretty_array_like(&fields, false))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1185,7 +1179,7 @@ fn create_vtable_func(element: &Elem) -> TokenStream {
|
||||
if id == ::std::any::TypeId::of::<dyn #capability>() {
|
||||
let vtable = unsafe {
|
||||
let dangling = ::std::ptr::NonNull::<#ident>::dangling().as_ptr() as *const dyn #capability;
|
||||
::typst::model::fat::vtable(dangling)
|
||||
::typst::util::fat::vtable(dangling)
|
||||
};
|
||||
return Some(vtable);
|
||||
}
|
||||
@ -1224,20 +1218,20 @@ fn create_param_info(field: &Field) -> TokenStream {
|
||||
quote! {
|
||||
|| {
|
||||
let typed: #default_ty = #default;
|
||||
::typst::eval::IntoValue::into_value(typed)
|
||||
#foundations::IntoValue::into_value(typed)
|
||||
}
|
||||
}
|
||||
}));
|
||||
let ty = if *variadic {
|
||||
quote! { <#ty as ::typst::eval::Container>::Inner }
|
||||
quote! { <#ty as #foundations::Container>::Inner }
|
||||
} else {
|
||||
quote! { #ty }
|
||||
};
|
||||
quote! {
|
||||
::typst::eval::ParamInfo {
|
||||
#foundations::ParamInfo {
|
||||
name: #name,
|
||||
docs: #docs,
|
||||
input: <#ty as ::typst::eval::Reflect>::input(),
|
||||
input: <#ty as #foundations::Reflect>::input(),
|
||||
default: #default,
|
||||
positional: #positional,
|
||||
named: #named,
|
||||
|
@ -1,4 +1,14 @@
|
||||
use super::*;
|
||||
use heck::ToKebabCase;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::{parse_quote, Ident, Result};
|
||||
|
||||
use crate::util::{
|
||||
determine_name_and_title, documentation, foundations, has_attr, kw, parse_attr,
|
||||
parse_flag, parse_key_value, parse_string, parse_string_array, quote_option,
|
||||
validate_attrs,
|
||||
};
|
||||
|
||||
/// Expand the `#[func]` macro.
|
||||
pub fn func(stream: TokenStream, item: &syn::ItemFn) -> Result<TokenStream> {
|
||||
@ -191,8 +201,6 @@ fn parse_param(
|
||||
|
||||
/// Produce the function's definition.
|
||||
fn create(func: &Func, item: &syn::ItemFn) -> TokenStream {
|
||||
let eval = quote! { ::typst::eval };
|
||||
|
||||
let Func { docs, vis, ident, .. } = func;
|
||||
let item = rewrite_fn_item(item);
|
||||
let ty = create_func_ty(func);
|
||||
@ -200,9 +208,9 @@ fn create(func: &Func, item: &syn::ItemFn) -> TokenStream {
|
||||
|
||||
let creator = if ty.is_some() {
|
||||
quote! {
|
||||
impl #eval::NativeFunc for #ident {
|
||||
fn data() -> &'static #eval::NativeFuncData {
|
||||
static DATA: #eval::NativeFuncData = #data;
|
||||
impl #foundations::NativeFunc for #ident {
|
||||
fn data() -> &'static #foundations::NativeFuncData {
|
||||
static DATA: #foundations::NativeFuncData = #data;
|
||||
&DATA
|
||||
}
|
||||
}
|
||||
@ -211,8 +219,8 @@ fn create(func: &Func, item: &syn::ItemFn) -> TokenStream {
|
||||
let ident_data = quote::format_ident!("{ident}_data");
|
||||
quote! {
|
||||
#[doc(hidden)]
|
||||
#vis fn #ident_data() -> &'static #eval::NativeFuncData {
|
||||
static DATA: #eval::NativeFuncData = #data;
|
||||
#vis fn #ident_data() -> &'static #foundations::NativeFuncData {
|
||||
static DATA: #foundations::NativeFuncData = #data;
|
||||
&DATA
|
||||
}
|
||||
}
|
||||
@ -231,8 +239,6 @@ fn create(func: &Func, item: &syn::ItemFn) -> TokenStream {
|
||||
|
||||
/// Create native function data for the function.
|
||||
fn create_func_data(func: &Func) -> TokenStream {
|
||||
let eval = quote! { ::typst::eval };
|
||||
|
||||
let Func {
|
||||
ident,
|
||||
name,
|
||||
@ -247,30 +253,30 @@ fn create_func_data(func: &Func) -> TokenStream {
|
||||
} = func;
|
||||
|
||||
let scope = if *scope {
|
||||
quote! { <#ident as #eval::NativeScope>::scope() }
|
||||
quote! { <#ident as #foundations::NativeScope>::scope() }
|
||||
} else {
|
||||
quote! { #eval::Scope::new() }
|
||||
quote! { #foundations::Scope::new() }
|
||||
};
|
||||
|
||||
let closure = create_wrapper_closure(func);
|
||||
let params = func.special.self_.iter().chain(&func.params).map(create_param_info);
|
||||
|
||||
let name = if *constructor {
|
||||
quote! { <#parent as #eval::NativeType>::NAME }
|
||||
quote! { <#parent as #foundations::NativeType>::NAME }
|
||||
} else {
|
||||
quote! { #name }
|
||||
};
|
||||
|
||||
quote! {
|
||||
#eval::NativeFuncData {
|
||||
#foundations::NativeFuncData {
|
||||
function: #closure,
|
||||
name: #name,
|
||||
title: #title,
|
||||
docs: #docs,
|
||||
keywords: &[#(#keywords),*],
|
||||
scope: #eval::Lazy::new(|| #scope),
|
||||
params: #eval::Lazy::new(|| ::std::vec![#(#params),*]),
|
||||
returns: #eval::Lazy::new(|| <#returns as #eval::Reflect>::output()),
|
||||
scope: #foundations::Lazy::new(|| #scope),
|
||||
params: #foundations::Lazy::new(|| ::std::vec![#(#params),*]),
|
||||
returns: #foundations::Lazy::new(|| <#returns as #foundations::Reflect>::output()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -335,7 +341,7 @@ fn create_wrapper_closure(func: &Func) -> TokenStream {
|
||||
#handlers
|
||||
#finish
|
||||
let output = #call;
|
||||
::typst::eval::IntoResult::into_result(output, args.span)
|
||||
#foundations::IntoResult::into_result(output, args.span)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -346,7 +352,7 @@ fn create_param_info(param: &Param) -> TokenStream {
|
||||
let positional = !named;
|
||||
let required = !named && default.is_none();
|
||||
let ty = if *variadic || (*named && default.is_none()) {
|
||||
quote! { <#ty as ::typst::eval::Container>::Inner }
|
||||
quote! { <#ty as #foundations::Container>::Inner }
|
||||
} else {
|
||||
quote! { #ty }
|
||||
};
|
||||
@ -354,15 +360,15 @@ fn create_param_info(param: &Param) -> TokenStream {
|
||||
quote! {
|
||||
|| {
|
||||
let typed: #ty = #default;
|
||||
::typst::eval::IntoValue::into_value(typed)
|
||||
#foundations::IntoValue::into_value(typed)
|
||||
}
|
||||
}
|
||||
}));
|
||||
quote! {
|
||||
::typst::eval::ParamInfo {
|
||||
#foundations::ParamInfo {
|
||||
name: #name,
|
||||
docs: #docs,
|
||||
input: <#ty as ::typst::eval::Reflect>::input(),
|
||||
input: <#ty as #foundations::Reflect>::input(),
|
||||
default: #default,
|
||||
positional: #positional,
|
||||
named: #named,
|
||||
|
@ -5,22 +5,15 @@ extern crate proc_macro;
|
||||
#[macro_use]
|
||||
mod util;
|
||||
mod cast;
|
||||
mod category;
|
||||
mod elem;
|
||||
mod func;
|
||||
mod scope;
|
||||
mod symbols;
|
||||
mod ty;
|
||||
|
||||
use heck::*;
|
||||
use proc_macro::TokenStream as BoundaryStream;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::ext::IdentExt;
|
||||
use syn::parse::{Parse, ParseStream, Parser};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::{parse_quote, DeriveInput, Ident, Result, Token};
|
||||
|
||||
use self::util::*;
|
||||
use syn::DeriveInput;
|
||||
|
||||
/// Makes a native Rust function usable as a Typst function.
|
||||
///
|
||||
@ -190,9 +183,6 @@ pub fn ty(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
|
||||
/// - `#[synthesized]`: The field cannot be specified in a constructor or set
|
||||
/// rule. Instead, it is added to an element before its show rule runs
|
||||
/// through the `Synthesize` trait.
|
||||
/// - `#[variant]`: Allows setting the ID of a field's variant. This is used
|
||||
/// for fields that are accessed in `typst` and not `typst-library`. It gives
|
||||
/// the field a stable ID that can be used to access it.
|
||||
/// - `#[ghost]`: Allows creating fields that are only present in the style chain,
|
||||
/// this means that they *cannot* be accessed by the user, they cannot be set
|
||||
/// on an individual instantiated element, and must be set via the style chain.
|
||||
@ -256,6 +246,15 @@ pub fn scope(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Defines a category of definitions.
|
||||
#[proc_macro_attribute]
|
||||
pub fn category(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
|
||||
let item = syn::parse_macro_input!(item as syn::Item);
|
||||
category::category(stream.into(), item)
|
||||
.unwrap_or_else(|err| err.to_compile_error())
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Implements `Reflect`, `FromValue`, and `IntoValue` for a type.
|
||||
///
|
||||
/// - `Reflect` makes Typst's runtime aware of the type's characteristics.
|
||||
|
@ -1,6 +1,9 @@
|
||||
use heck::ToKebabCase;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{parse_quote, Result};
|
||||
|
||||
use super::*;
|
||||
use crate::util::{foundations, BareType};
|
||||
|
||||
/// Expand the `#[scope]` macro.
|
||||
pub fn scope(_: TokenStream, item: syn::Item) -> Result<TokenStream> {
|
||||
@ -8,7 +11,6 @@ pub fn scope(_: TokenStream, item: syn::Item) -> Result<TokenStream> {
|
||||
bail!(item, "expected module or impl item");
|
||||
};
|
||||
|
||||
let eval = quote! { ::typst::eval };
|
||||
let self_ty = &item.self_ty;
|
||||
|
||||
let mut definitions = vec![];
|
||||
@ -43,13 +45,13 @@ pub fn scope(_: TokenStream, item: syn::Item) -> Result<TokenStream> {
|
||||
Ok(quote! {
|
||||
#base
|
||||
|
||||
impl #eval::NativeScope for #self_ty {
|
||||
fn constructor() -> ::std::option::Option<&'static #eval::NativeFuncData> {
|
||||
impl #foundations::NativeScope for #self_ty {
|
||||
fn constructor() -> ::std::option::Option<&'static #foundations::NativeFuncData> {
|
||||
#constructor
|
||||
}
|
||||
|
||||
fn scope() -> #eval::Scope {
|
||||
let mut scope = #eval::Scope::deduplicating();
|
||||
fn scope() -> #foundations::Scope {
|
||||
let mut scope = #foundations::Scope::deduplicating();
|
||||
#(#definitions;)*
|
||||
scope
|
||||
}
|
||||
@ -92,7 +94,7 @@ fn handle_fn(self_ty: &syn::Type, item: &mut syn::ImplItemFn) -> Result<FnKind>
|
||||
}
|
||||
syn::Meta::List(list) => {
|
||||
let tokens = &list.tokens;
|
||||
let meta: super::func::Meta = syn::parse2(tokens.clone())?;
|
||||
let meta: crate::func::Meta = syn::parse2(tokens.clone())?;
|
||||
list.tokens = quote! { #tokens, parent = #self_ty };
|
||||
if meta.constructor {
|
||||
return Ok(FnKind::Constructor(quote! { Some(#self_ty::#ident_data()) }));
|
||||
@ -135,7 +137,7 @@ fn rewrite_primitive_base(item: &syn::ItemImpl, ident: &syn::Ident) -> TokenStre
|
||||
let ident_data = quote::format_ident!("{}_data", sig.ident);
|
||||
sigs.push(quote! { #sig; });
|
||||
sigs.push(quote! {
|
||||
fn #ident_data() -> &'static ::typst::eval::NativeFuncData;
|
||||
fn #ident_data() -> &'static #foundations::NativeFuncData;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,9 @@
|
||||
use super::*;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::ext::IdentExt;
|
||||
use syn::parse::{Parse, ParseStream, Parser};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::{Ident, Result, Token};
|
||||
|
||||
/// Expand the `symbols!` macro.
|
||||
pub fn symbols(stream: TokenStream) -> Result<TokenStream> {
|
||||
@ -7,7 +12,7 @@ pub fn symbols(stream: TokenStream) -> Result<TokenStream> {
|
||||
let pairs = list.iter().map(|symbol| {
|
||||
let name = symbol.name.to_string();
|
||||
let kind = match &symbol.kind {
|
||||
Kind::Single(c) => quote! { typst::eval::Symbol::single(#c), },
|
||||
Kind::Single(c) => quote! { ::typst::symbols::Symbol::single(#c), },
|
||||
Kind::Multiple(variants) => {
|
||||
let variants = variants.iter().map(|variant| {
|
||||
let name = &variant.name;
|
||||
@ -15,7 +20,7 @@ pub fn symbols(stream: TokenStream) -> Result<TokenStream> {
|
||||
quote! { (#name, #c) }
|
||||
});
|
||||
quote! {
|
||||
typst::eval::Symbol::list(&[#(#variants),*])
|
||||
::typst::symbols::Symbol::list(&[#(#variants),*])
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1,6 +1,12 @@
|
||||
use syn::Attribute;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::{Attribute, Ident, Result};
|
||||
|
||||
use super::*;
|
||||
use crate::util::{
|
||||
determine_name_and_title, documentation, foundations, kw, parse_flag, parse_string,
|
||||
parse_string_array, BareType,
|
||||
};
|
||||
|
||||
/// Expand the `#[ty]` macro.
|
||||
pub fn ty(stream: TokenStream, item: syn::Item) -> Result<TokenStream> {
|
||||
@ -68,44 +74,42 @@ fn parse(meta: Meta, ident: Ident, attrs: &[Attribute]) -> Result<Type> {
|
||||
|
||||
/// Produce the output of the macro.
|
||||
fn create(ty: &Type, item: Option<&syn::Item>) -> TokenStream {
|
||||
let eval = quote! { ::typst::eval };
|
||||
|
||||
let Type {
|
||||
ident, name, long, title, docs, keywords, scope, ..
|
||||
} = ty;
|
||||
|
||||
let constructor = if *scope {
|
||||
quote! { <#ident as #eval::NativeScope>::constructor() }
|
||||
quote! { <#ident as #foundations::NativeScope>::constructor() }
|
||||
} else {
|
||||
quote! { None }
|
||||
};
|
||||
|
||||
let scope = if *scope {
|
||||
quote! { <#ident as #eval::NativeScope>::scope() }
|
||||
quote! { <#ident as #foundations::NativeScope>::scope() }
|
||||
} else {
|
||||
quote! { #eval::Scope::new() }
|
||||
quote! { #foundations::Scope::new() }
|
||||
};
|
||||
|
||||
let data = quote! {
|
||||
#eval::NativeTypeData {
|
||||
#foundations::NativeTypeData {
|
||||
name: #name,
|
||||
long_name: #long,
|
||||
title: #title,
|
||||
docs: #docs,
|
||||
keywords: &[#(#keywords),*],
|
||||
constructor: #eval::Lazy::new(|| #constructor),
|
||||
scope: #eval::Lazy::new(|| #scope),
|
||||
constructor: #foundations::Lazy::new(|| #constructor),
|
||||
scope: #foundations::Lazy::new(|| #scope),
|
||||
}
|
||||
};
|
||||
|
||||
quote! {
|
||||
#item
|
||||
|
||||
impl #eval::NativeType for #ident {
|
||||
impl #foundations::NativeType for #ident {
|
||||
const NAME: &'static str = #name;
|
||||
|
||||
fn data() -> &'static #eval::NativeTypeData {
|
||||
static DATA: #eval::NativeTypeData = #data;
|
||||
fn data() -> &'static #foundations::NativeTypeData {
|
||||
static DATA: #foundations::NativeTypeData = #data;
|
||||
&DATA
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
use quote::ToTokens;
|
||||
use heck::{ToKebabCase, ToTitleCase};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::token::Token;
|
||||
use syn::Attribute;
|
||||
|
||||
use super::*;
|
||||
use syn::{Attribute, Ident, Result, Token};
|
||||
|
||||
/// Return an error at the given item.
|
||||
macro_rules! bail {
|
||||
@ -199,6 +200,16 @@ impl<T: Parse> Parse for Array<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Shorthand for `::typst::foundations`.
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct foundations;
|
||||
|
||||
impl quote::ToTokens for foundations {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
quote! { ::typst::foundations }.to_tokens(tokens);
|
||||
}
|
||||
}
|
||||
|
||||
/// For parsing attributes of the form:
|
||||
/// #[attr(
|
||||
/// statement;
|
||||
@ -220,15 +231,6 @@ impl Parse for BlockWithReturn {
|
||||
}
|
||||
}
|
||||
|
||||
pub mod kw {
|
||||
syn::custom_keyword!(name);
|
||||
syn::custom_keyword!(title);
|
||||
syn::custom_keyword!(scope);
|
||||
syn::custom_keyword!(constructor);
|
||||
syn::custom_keyword!(keywords);
|
||||
syn::custom_keyword!(parent);
|
||||
}
|
||||
|
||||
/// Parse a bare `type Name;` item.
|
||||
pub struct BareType {
|
||||
pub attrs: Vec<Attribute>,
|
||||
@ -239,7 +241,7 @@ pub struct BareType {
|
||||
|
||||
impl Parse for BareType {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
Ok(BareType {
|
||||
Ok(Self {
|
||||
attrs: input.call(Attribute::parse_outer)?,
|
||||
type_token: input.parse()?,
|
||||
ident: input.parse()?,
|
||||
@ -247,3 +249,12 @@ impl Parse for BareType {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub mod kw {
|
||||
syn::custom_keyword!(name);
|
||||
syn::custom_keyword!(title);
|
||||
syn::custom_keyword!(scope);
|
||||
syn::custom_keyword!(constructor);
|
||||
syn::custom_keyword!(keywords);
|
||||
syn::custom_keyword!(parent);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use pdf_writer::types::DeviceNSubtype;
|
||||
use pdf_writer::{writers, Chunk, Dict, Filter, Name, Ref};
|
||||
use typst::geom::{Color, ColorSpace, Paint};
|
||||
use typst::visualize::{Color, ColorSpace, Paint};
|
||||
|
||||
use crate::deflate;
|
||||
use crate::page::{PageContext, Transforms};
|
||||
|
@ -5,7 +5,7 @@ use ecow::{eco_format, EcoString};
|
||||
use pdf_writer::types::{CidFontType, FontFlags, SystemInfo, UnicodeCmap};
|
||||
use pdf_writer::{Filter, Finish, Name, Rect, Str};
|
||||
use ttf_parser::{name_id, GlyphId, Tag};
|
||||
use typst::font::Font;
|
||||
use typst::text::Font;
|
||||
use typst::util::SliceExt;
|
||||
use unicode_properties::{GeneralCategory, UnicodeGeneralCategory};
|
||||
|
||||
|
@ -5,9 +5,10 @@ use ecow::{eco_format, EcoString};
|
||||
use pdf_writer::types::{ColorSpaceOperand, FunctionShadingType};
|
||||
use pdf_writer::writers::StreamShadingType;
|
||||
use pdf_writer::{Filter, Finish, Name, Ref};
|
||||
use typst::geom::{
|
||||
Abs, Angle, Color, ColorSpace, ConicGradient, Gradient, Numeric, Point, Quadrant,
|
||||
Ratio, Relative, Transform, WeightedColor,
|
||||
use typst::layout::{Abs, Angle, Point, Quadrant, Ratio, Transform};
|
||||
use typst::util::Numeric;
|
||||
use typst::visualize::{
|
||||
Color, ColorSpace, ConicGradient, Gradient, GradientRelative, WeightedColor,
|
||||
};
|
||||
|
||||
use crate::color::{ColorSpaceExt, PaintEncode, QuantizedColor};
|
||||
@ -301,8 +302,8 @@ fn register_gradient(
|
||||
transforms.size.y = Abs::pt(1.0);
|
||||
}
|
||||
let size = match gradient.unwrap_relative(on_text) {
|
||||
Relative::Self_ => transforms.size,
|
||||
Relative::Parent => transforms.container_size,
|
||||
GradientRelative::Self_ => transforms.size,
|
||||
GradientRelative::Parent => transforms.container_size,
|
||||
};
|
||||
|
||||
let (offset_x, offset_y) = match gradient {
|
||||
@ -316,8 +317,8 @@ fn register_gradient(
|
||||
let rotation = gradient.angle().unwrap_or_else(Angle::zero);
|
||||
|
||||
let transform = match gradient.unwrap_relative(on_text) {
|
||||
Relative::Self_ => transforms.transform,
|
||||
Relative::Parent => transforms.container_transform,
|
||||
GradientRelative::Self_ => transforms.transform,
|
||||
GradientRelative::Parent => transforms.container_transform,
|
||||
};
|
||||
|
||||
let scale_offset = match gradient {
|
||||
|
@ -3,9 +3,10 @@ use std::io::Cursor;
|
||||
|
||||
use image::{DynamicImage, GenericImageView, Rgba};
|
||||
use pdf_writer::{Chunk, Filter, Finish, Ref};
|
||||
use typst::geom::ColorSpace;
|
||||
use typst::image::{Image, ImageKind, RasterFormat, RasterImage, SvgImage};
|
||||
use typst::util::Deferred;
|
||||
use typst::visualize::{
|
||||
ColorSpace, Image, ImageKind, RasterFormat, RasterImage, SvgImage,
|
||||
};
|
||||
|
||||
use crate::{deflate, PdfContext};
|
||||
|
||||
|
@ -16,13 +16,13 @@ use base64::Engine;
|
||||
use ecow::{eco_format, EcoString};
|
||||
use pdf_writer::types::Direction;
|
||||
use pdf_writer::{Finish, Name, Pdf, Ref, TextStr};
|
||||
use typst::doc::{Document, Lang};
|
||||
use typst::eval::Datetime;
|
||||
use typst::font::Font;
|
||||
use typst::geom::{Abs, Dir, Em};
|
||||
use typst::image::Image;
|
||||
use typst::model::Introspector;
|
||||
use typst::foundations::Datetime;
|
||||
use typst::introspection::Introspector;
|
||||
use typst::layout::{Abs, Dir, Em};
|
||||
use typst::model::Document;
|
||||
use typst::text::{Font, Lang};
|
||||
use typst::util::Deferred;
|
||||
use typst::visualize::Image;
|
||||
use xmp_writer::{DateTime, LangId, RenditionClass, Timezone, XmpWriter};
|
||||
|
||||
use crate::color::ColorSpaces;
|
||||
|
@ -1,9 +1,9 @@
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use pdf_writer::{Finish, Ref, TextStr};
|
||||
use typst::eval::{item, Smart};
|
||||
use typst::geom::Abs;
|
||||
use typst::model::Content;
|
||||
use typst::foundations::{Content, NativeElement, Smart};
|
||||
use typst::layout::Abs;
|
||||
use typst::model::HeadingElem;
|
||||
|
||||
use crate::{AbsExt, PdfContext};
|
||||
|
||||
@ -18,7 +18,7 @@ pub(crate) fn write_outline(ctx: &mut PdfContext) -> Option<Ref> {
|
||||
// Therefore, its next descendant must be added at its level, which is
|
||||
// enforced in the manner shown below.
|
||||
let mut last_skipped_level = None;
|
||||
for heading in ctx.introspector.query(&item!(heading_elem).select()).iter() {
|
||||
for heading in ctx.introspector.query(&HeadingElem::elem().select()).iter() {
|
||||
let leaf = HeadingNode::leaf((**heading).clone());
|
||||
|
||||
if leaf.bookmarked {
|
||||
|
@ -8,16 +8,17 @@ use pdf_writer::types::{
|
||||
};
|
||||
use pdf_writer::writers::PageLabel;
|
||||
use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str, TextStr};
|
||||
use typst::doc::{
|
||||
Destination, Frame, FrameItem, GroupItem, Meta, PdfPageLabel, PdfPageLabelStyle,
|
||||
TextItem,
|
||||
use typst::introspection::Meta;
|
||||
use typst::layout::{
|
||||
Abs, Em, Frame, FrameItem, GroupItem, PdfPageLabel, PdfPageLabelStyle, Point, Ratio,
|
||||
Size, Transform,
|
||||
};
|
||||
use typst::font::Font;
|
||||
use typst::geom::{
|
||||
self, Abs, Em, FixedStroke, Geometry, LineCap, LineJoin, Numeric, Paint, Point,
|
||||
Ratio, Shape, Size, Transform,
|
||||
use typst::model::Destination;
|
||||
use typst::text::{Font, TextItem};
|
||||
use typst::util::Numeric;
|
||||
use typst::visualize::{
|
||||
FixedStroke, Geometry, Image, LineCap, LineJoin, Paint, Path, PathItem, Shape,
|
||||
};
|
||||
use typst::image::Image;
|
||||
|
||||
use crate::color::PaintEncode;
|
||||
use crate::extg::ExtGState;
|
||||
@ -581,7 +582,7 @@ fn write_text(ctx: &mut PageContext, pos: Point, text: &TextItem) {
|
||||
adjustment = Em::zero();
|
||||
}
|
||||
|
||||
let cid = super::font::glyph_cid(&text.font, glyph.id);
|
||||
let cid = crate::font::glyph_cid(&text.font, glyph.id);
|
||||
encoded.push((cid >> 8) as u8);
|
||||
encoded.push((cid & 0xff) as u8);
|
||||
|
||||
@ -656,16 +657,16 @@ fn write_shape(ctx: &mut PageContext, pos: Point, shape: &Shape) {
|
||||
}
|
||||
|
||||
/// Encode a bezier path into the content stream.
|
||||
fn write_path(ctx: &mut PageContext, x: f32, y: f32, path: &geom::Path) {
|
||||
fn write_path(ctx: &mut PageContext, x: f32, y: f32, path: &Path) {
|
||||
for elem in &path.0 {
|
||||
match elem {
|
||||
geom::PathItem::MoveTo(p) => {
|
||||
PathItem::MoveTo(p) => {
|
||||
ctx.content.move_to(x + p.x.to_f32(), y + p.y.to_f32())
|
||||
}
|
||||
geom::PathItem::LineTo(p) => {
|
||||
PathItem::LineTo(p) => {
|
||||
ctx.content.line_to(x + p.x.to_f32(), y + p.y.to_f32())
|
||||
}
|
||||
geom::PathItem::CubicTo(p1, p2, p3) => ctx.content.cubic_to(
|
||||
PathItem::CubicTo(p1, p2, p3) => ctx.content.cubic_to(
|
||||
x + p1.x.to_f32(),
|
||||
y + p1.y.to_f32(),
|
||||
x + p2.x.to_f32(),
|
||||
@ -673,7 +674,7 @@ fn write_path(ctx: &mut PageContext, x: f32, y: f32, path: &geom::Path) {
|
||||
x + p3.x.to_f32(),
|
||||
y + p3.y.to_f32(),
|
||||
),
|
||||
geom::PathItem::ClosePath => ctx.content.close_path(),
|
||||
PathItem::ClosePath => ctx.content.close_path(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -9,13 +9,15 @@ use pixglyph::Bitmap;
|
||||
use resvg::tiny_skia::IntRect;
|
||||
use tiny_skia as sk;
|
||||
use ttf_parser::{GlyphId, OutlineBuilder};
|
||||
use typst::doc::{Frame, FrameItem, FrameKind, GroupItem, Meta, TextItem};
|
||||
use typst::font::Font;
|
||||
use typst::geom::{
|
||||
self, Abs, Axes, Color, FixedStroke, Geometry, Gradient, LineCap, LineJoin, Paint,
|
||||
PathItem, Point, Ratio, Relative, Shape, Size, Transform,
|
||||
use typst::introspection::Meta;
|
||||
use typst::layout::{
|
||||
Abs, Axes, Frame, FrameItem, FrameKind, GroupItem, Point, Ratio, Size, Transform,
|
||||
};
|
||||
use typst::text::{Font, TextItem};
|
||||
use typst::visualize::{
|
||||
Color, FixedStroke, Geometry, Gradient, GradientRelative, Image, ImageKind, LineCap,
|
||||
LineJoin, Paint, Path, PathItem, RasterFormat, Shape,
|
||||
};
|
||||
use typst::image::{Image, ImageKind, RasterFormat};
|
||||
use usvg::{NodeExt, TreeParsing};
|
||||
|
||||
/// Export a frame into a raster image.
|
||||
@ -634,7 +636,7 @@ fn render_shape(canvas: &mut sk::Pixmap, state: State, shape: &Shape) -> Option<
|
||||
}
|
||||
|
||||
/// Convert a Typst path into a tiny-skia path.
|
||||
fn convert_path(path: &geom::Path) -> Option<sk::Path> {
|
||||
fn convert_path(path: &Path) -> Option<sk::Path> {
|
||||
let mut builder = sk::PathBuilder::new();
|
||||
for elem in &path.0 {
|
||||
match elem {
|
||||
@ -773,13 +775,13 @@ impl<'a> GradientSampler<'a> {
|
||||
) -> Self {
|
||||
let relative = gradient.unwrap_relative(on_text);
|
||||
let container_size = match relative {
|
||||
Relative::Self_ => item_size,
|
||||
Relative::Parent => state.size,
|
||||
GradientRelative::Self_ => item_size,
|
||||
GradientRelative::Parent => state.size,
|
||||
};
|
||||
|
||||
let fill_transform = match relative {
|
||||
Relative::Self_ => sk::Transform::identity(),
|
||||
Relative::Parent => state.container_transform.invert().unwrap(),
|
||||
GradientRelative::Self_ => sk::Transform::identity(),
|
||||
GradientRelative::Parent => state.container_transform.invert().unwrap(),
|
||||
};
|
||||
|
||||
Self {
|
||||
@ -857,13 +859,13 @@ fn to_sk_paint<'a>(
|
||||
Paint::Gradient(gradient) => {
|
||||
let relative = gradient.unwrap_relative(on_text);
|
||||
let container_size = match relative {
|
||||
Relative::Self_ => item_size,
|
||||
Relative::Parent => state.size,
|
||||
GradientRelative::Self_ => item_size,
|
||||
GradientRelative::Parent => state.size,
|
||||
};
|
||||
|
||||
let fill_transform = match relative {
|
||||
Relative::Self_ => fill_transform.unwrap_or_default(),
|
||||
Relative::Parent => state
|
||||
GradientRelative::Self_ => fill_transform.unwrap_or_default(),
|
||||
GradientRelative::Parent => state
|
||||
.container_transform
|
||||
.post_concat(state.transform.invert().unwrap()),
|
||||
};
|
||||
|
@ -6,16 +6,18 @@ use std::io::Read;
|
||||
use base64::Engine;
|
||||
use ecow::{eco_format, EcoString};
|
||||
use ttf_parser::{GlyphId, OutlineBuilder};
|
||||
use typst::doc::{Frame, FrameItem, FrameKind, GroupItem, TextItem};
|
||||
use typst::eval::Repr;
|
||||
use typst::font::Font;
|
||||
use typst::geom::{
|
||||
self, Abs, Angle, Axes, Color, FixedStroke, Geometry, Gradient, LineCap, LineJoin,
|
||||
Paint, PathItem, Point, Quadrant, Ratio, RatioOrAngle, Relative, Shape, Size,
|
||||
Transform,
|
||||
use typst::foundations::Repr;
|
||||
use typst::layout::{
|
||||
Abs, Angle, Axes, Frame, FrameItem, FrameKind, GroupItem, Point, Quadrant, Ratio,
|
||||
Size, Transform,
|
||||
};
|
||||
use typst::image::{Image, ImageFormat, RasterFormat, VectorFormat};
|
||||
use typst::text::{Font, TextItem};
|
||||
use typst::util::hash128;
|
||||
use typst::visualize::{
|
||||
Color, FixedStroke, Geometry, Gradient, GradientRelative, Image, ImageFormat,
|
||||
LineCap, LineJoin, Paint, Path, PathItem, RasterFormat, RatioOrAngle, Shape,
|
||||
VectorFormat,
|
||||
};
|
||||
use xmlwriter::XmlWriter;
|
||||
|
||||
/// The number of segments in a conic gradient.
|
||||
@ -432,8 +434,8 @@ impl SVGRenderer {
|
||||
};
|
||||
|
||||
match gradient.unwrap_relative(true) {
|
||||
Relative::Self_ => Transform::scale(Ratio::one(), Ratio::one()),
|
||||
Relative::Parent => Transform::scale(
|
||||
GradientRelative::Self_ => Transform::scale(Ratio::one(), Ratio::one()),
|
||||
GradientRelative::Parent => Transform::scale(
|
||||
Ratio::new(state.size.x.to_pt()),
|
||||
Ratio::new(state.size.y.to_pt()),
|
||||
)
|
||||
@ -488,11 +490,11 @@ impl SVGRenderer {
|
||||
|
||||
if let Paint::Gradient(gradient) = paint {
|
||||
match gradient.unwrap_relative(false) {
|
||||
Relative::Self_ => Transform::scale(
|
||||
GradientRelative::Self_ => Transform::scale(
|
||||
Ratio::new(shape_size.x.to_pt()),
|
||||
Ratio::new(shape_size.y.to_pt()),
|
||||
),
|
||||
Relative::Parent => Transform::scale(
|
||||
GradientRelative::Parent => Transform::scale(
|
||||
Ratio::new(state.size.x.to_pt()),
|
||||
Ratio::new(state.size.y.to_pt()),
|
||||
)
|
||||
@ -517,8 +519,8 @@ impl SVGRenderer {
|
||||
|
||||
if let Paint::Gradient(gradient) = paint {
|
||||
match gradient.unwrap_relative(false) {
|
||||
Relative::Self_ => shape_size,
|
||||
Relative::Parent => state.size,
|
||||
GradientRelative::Self_ => shape_size,
|
||||
GradientRelative::Parent => state.size,
|
||||
}
|
||||
} else {
|
||||
shape_size
|
||||
@ -1047,7 +1049,7 @@ fn convert_geometry_to_path(geometry: &Geometry) -> EcoString {
|
||||
builder.0
|
||||
}
|
||||
|
||||
fn convert_path(path: &geom::Path) -> EcoString {
|
||||
fn convert_path(path: &Path) -> EcoString {
|
||||
let mut builder = SvgPathBuilder::default();
|
||||
for item in &path.0 {
|
||||
match item {
|
||||
|
@ -11,7 +11,7 @@ use crate::{
|
||||
/// ultimately reparsed.
|
||||
///
|
||||
/// The high-level API for this function is
|
||||
/// [`Source::edit`](super::Source::edit).
|
||||
/// [`Source::edit`](crate::Source::edit).
|
||||
pub fn reparse(
|
||||
root: &mut SyntaxNode,
|
||||
text: &str,
|
||||
|
@ -7,7 +7,7 @@ use crate::FileId;
|
||||
/// A unique identifier for a syntax node.
|
||||
///
|
||||
/// This is used throughout the compiler to track which source section an error
|
||||
/// or element stems from. Can be [mapped back](super::Source::range) to a byte
|
||||
/// or element stems from. Can be [mapped back](crate::Source::range) to a byte
|
||||
/// range for user facing display.
|
||||
///
|
||||
/// During editing, the span values stay mostly stable, even for nodes behind an
|
||||
@ -76,7 +76,7 @@ impl Span {
|
||||
Some(FileId::from_raw(bits))
|
||||
}
|
||||
|
||||
/// The unique number of the span within its [`Source`](super::Source).
|
||||
/// The unique number of the span within its [`Source`](crate::Source).
|
||||
pub const fn number(self) -> u64 {
|
||||
self.0.get() & ((1 << Self::BITS) - 1)
|
||||
}
|
||||
|
@ -18,14 +18,26 @@ bench = false
|
||||
[dependencies]
|
||||
typst-macros = { workspace = true }
|
||||
typst-syntax = { workspace = true }
|
||||
az = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
chinese-number = { workspace = true }
|
||||
ciborium = { workspace = true }
|
||||
comemo = { workspace = true }
|
||||
csv = { workspace = true }
|
||||
ecow = { workspace = true}
|
||||
fontdb = { workspace = true }
|
||||
hayagriva = { workspace = true }
|
||||
hypher = { workspace = true }
|
||||
icu_properties = { workspace = true }
|
||||
icu_provider = { workspace = true }
|
||||
icu_provider_adapters = { workspace = true }
|
||||
icu_provider_blob = { workspace = true }
|
||||
icu_segmenter = { workspace = true }
|
||||
image = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
kurbo = { workspace = true }
|
||||
lasso = { workspace = true }
|
||||
lipsum = { workspace = true }
|
||||
log = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
palette = { workspace = true }
|
||||
@ -34,13 +46,19 @@ regex = { workspace = true }
|
||||
roxmltree = { workspace = true }
|
||||
rustybuzz = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde_yaml = { workspace = true }
|
||||
siphasher = { workspace = true }
|
||||
smallvec = { workspace = true }
|
||||
syntect = { workspace = true }
|
||||
time = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
ttf-parser = { workspace = true }
|
||||
typed-arena = { workspace = true }
|
||||
unicode-bidi = { workspace = true }
|
||||
unicode-math-class = { workspace = true }
|
||||
unicode-script = { workspace = true }
|
||||
unicode-segmentation = { workspace = true }
|
||||
usvg = { workspace = true }
|
||||
wasmi = { workspace = true }
|
||||
|
@ -42,10 +42,16 @@ macro_rules! __bail {
|
||||
}
|
||||
|
||||
#[doc(inline)]
|
||||
pub use crate::{__bail as bail, __error as error, __warning as warning};
|
||||
pub use crate::__bail as bail;
|
||||
#[doc(inline)]
|
||||
pub use crate::__error as error;
|
||||
#[doc(inline)]
|
||||
pub use crate::__warning as warning;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use ecow::{eco_format, EcoString};
|
||||
pub use ecow::eco_format;
|
||||
#[doc(hidden)]
|
||||
pub use ecow::EcoString;
|
||||
|
||||
/// Construct an [`EcoString`] or [`SourceDiagnostic`] with severity `Error`.
|
||||
#[macro_export]
|
||||
@ -159,6 +165,25 @@ impl From<SyntaxError> for SourceDiagnostic {
|
||||
}
|
||||
}
|
||||
|
||||
/// Holds delayed errors.
|
||||
#[derive(Default, Clone)]
|
||||
pub struct DelayedErrors(pub EcoVec<SourceDiagnostic>);
|
||||
|
||||
impl DelayedErrors {
|
||||
/// Create an empty list of delayed errors.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[comemo::track]
|
||||
impl DelayedErrors {
|
||||
/// Push a delayed error.
|
||||
pub fn push(&mut self, error: SourceDiagnostic) {
|
||||
self.0.push(error);
|
||||
}
|
||||
}
|
||||
|
||||
/// A part of a diagnostic's [trace](SourceDiagnostic::trace).
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub enum Tracepoint {
|
||||
|
99
crates/typst/src/eval/access.rs
Normal file
99
crates/typst/src/eval/access.rs
Normal file
@ -0,0 +1,99 @@
|
||||
use ecow::eco_format;
|
||||
|
||||
use crate::diag::{bail, At, Hint, SourceResult, Trace, Tracepoint};
|
||||
use crate::eval::{Eval, Vm};
|
||||
use crate::foundations::{call_method_access, is_accessor_method, Dict, Value};
|
||||
use crate::syntax::ast::{self, AstNode};
|
||||
|
||||
/// Access an expression mutably.
|
||||
pub(crate) trait Access {
|
||||
/// Access the value.
|
||||
fn access<'a>(self, vm: &'a mut Vm) -> SourceResult<&'a mut Value>;
|
||||
}
|
||||
|
||||
impl Access for ast::Expr<'_> {
|
||||
fn access<'a>(self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
|
||||
match self {
|
||||
Self::Ident(v) => v.access(vm),
|
||||
Self::Parenthesized(v) => v.access(vm),
|
||||
Self::FieldAccess(v) => v.access(vm),
|
||||
Self::FuncCall(v) => v.access(vm),
|
||||
_ => {
|
||||
let _ = self.eval(vm)?;
|
||||
bail!(self.span(), "cannot mutate a temporary value");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Access for ast::Ident<'_> {
|
||||
fn access<'a>(self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
|
||||
let span = self.span();
|
||||
let value = vm.scopes.get_mut(&self).at(span)?;
|
||||
if vm.inspected == Some(span) {
|
||||
vm.vt.tracer.value(value.clone());
|
||||
}
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Access for ast::Parenthesized<'_> {
|
||||
fn access<'a>(self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
|
||||
self.expr().access(vm)
|
||||
}
|
||||
}
|
||||
|
||||
impl Access for ast::FieldAccess<'_> {
|
||||
fn access<'a>(self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
|
||||
access_dict(vm, self)?.at_mut(self.field().get()).at(self.span())
|
||||
}
|
||||
}
|
||||
|
||||
impl Access for ast::FuncCall<'_> {
|
||||
fn access<'a>(self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
|
||||
if let ast::Expr::FieldAccess(access) = self.callee() {
|
||||
let method = access.field();
|
||||
if is_accessor_method(&method) {
|
||||
let span = self.span();
|
||||
let world = vm.world();
|
||||
let args = self.args().eval(vm)?;
|
||||
let value = access.target().access(vm)?;
|
||||
let result = call_method_access(value, &method, args, span);
|
||||
let point = || Tracepoint::Call(Some(method.get().clone()));
|
||||
return result.trace(world, point, span);
|
||||
}
|
||||
}
|
||||
|
||||
let _ = self.eval(vm)?;
|
||||
bail!(self.span(), "cannot mutate a temporary value");
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn access_dict<'a>(
|
||||
vm: &'a mut Vm,
|
||||
access: ast::FieldAccess,
|
||||
) -> SourceResult<&'a mut Dict> {
|
||||
match access.target().access(vm)? {
|
||||
Value::Dict(dict) => Ok(dict),
|
||||
value => {
|
||||
let ty = value.ty();
|
||||
let span = access.target().span();
|
||||
if matches!(
|
||||
value, // those types have their own field getters
|
||||
Value::Symbol(_) | Value::Content(_) | Value::Module(_) | Value::Func(_)
|
||||
) {
|
||||
bail!(span, "cannot mutate fields on {ty}");
|
||||
} else if crate::foundations::fields_on(ty).is_empty() {
|
||||
bail!(span, "{ty} does not have accessible fields");
|
||||
} else {
|
||||
// type supports static fields, which don't yet have
|
||||
// setters
|
||||
Err(eco_format!("fields on {ty} are not yet mutable"))
|
||||
.hint(eco_format!(
|
||||
"try creating a new {ty} with the updated field value instead"
|
||||
))
|
||||
.at(span)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
179
crates/typst/src/eval/binding.rs
Normal file
179
crates/typst/src/eval/binding.rs
Normal file
@ -0,0 +1,179 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::diag::{bail, At, SourceResult};
|
||||
use crate::eval::{Access, Eval, Vm};
|
||||
use crate::foundations::{Array, Dict, Value};
|
||||
use crate::syntax::ast::{self, AstNode};
|
||||
|
||||
impl Eval for ast::LetBinding<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "LetBinding::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let value = match self.init() {
|
||||
Some(expr) => expr.eval(vm)?,
|
||||
None => Value::None,
|
||||
};
|
||||
if vm.flow.is_some() {
|
||||
return Ok(Value::None);
|
||||
}
|
||||
|
||||
match self.kind() {
|
||||
ast::LetBindingKind::Normal(pattern) => destructure(vm, pattern, value)?,
|
||||
ast::LetBindingKind::Closure(ident) => vm.define(ident, value),
|
||||
}
|
||||
|
||||
Ok(Value::None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::DestructAssignment<'_> {
|
||||
type Output = Value;
|
||||
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let value = self.value().eval(vm)?;
|
||||
destructure_impl(vm, self.pattern(), value, |vm, expr, value| {
|
||||
let location = expr.access(vm)?;
|
||||
*location = value;
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(Value::None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Destructures a value into a pattern.
|
||||
pub(crate) fn destructure(
|
||||
vm: &mut Vm,
|
||||
pattern: ast::Pattern,
|
||||
value: Value,
|
||||
) -> SourceResult<()> {
|
||||
destructure_impl(vm, pattern, value, |vm, expr, value| match expr {
|
||||
ast::Expr::Ident(ident) => {
|
||||
vm.define(ident, value);
|
||||
Ok(())
|
||||
}
|
||||
_ => bail!(expr.span(), "nested patterns are currently not supported"),
|
||||
})
|
||||
}
|
||||
|
||||
/// Destruct the given value into the pattern and apply the function to each binding.
|
||||
#[tracing::instrument(skip_all)]
|
||||
fn destructure_impl<T>(
|
||||
vm: &mut Vm,
|
||||
pattern: ast::Pattern,
|
||||
value: Value,
|
||||
f: T,
|
||||
) -> SourceResult<()>
|
||||
where
|
||||
T: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<()>,
|
||||
{
|
||||
match pattern {
|
||||
ast::Pattern::Normal(expr) => {
|
||||
f(vm, expr, value)?;
|
||||
}
|
||||
ast::Pattern::Placeholder(_) => {}
|
||||
ast::Pattern::Destructuring(destruct) => match value {
|
||||
Value::Array(value) => destructure_array(vm, pattern, value, f, destruct)?,
|
||||
Value::Dict(value) => destructure_dict(vm, value, f, destruct)?,
|
||||
_ => bail!(pattern.span(), "cannot destructure {}", value.ty()),
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn destructure_array<F>(
|
||||
vm: &mut Vm,
|
||||
pattern: ast::Pattern,
|
||||
value: Array,
|
||||
f: F,
|
||||
destruct: ast::Destructuring,
|
||||
) -> SourceResult<()>
|
||||
where
|
||||
F: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<()>,
|
||||
{
|
||||
let mut i = 0;
|
||||
let len = value.as_slice().len();
|
||||
for p in destruct.bindings() {
|
||||
match p {
|
||||
ast::DestructuringKind::Normal(expr) => {
|
||||
let Ok(v) = value.at(i as i64, None) else {
|
||||
bail!(expr.span(), "not enough elements to destructure");
|
||||
};
|
||||
f(vm, expr, v)?;
|
||||
i += 1;
|
||||
}
|
||||
ast::DestructuringKind::Sink(spread) => {
|
||||
let sink_size = (1 + len).checked_sub(destruct.bindings().count());
|
||||
let sink = sink_size.and_then(|s| value.as_slice().get(i..i + s));
|
||||
if let (Some(sink_size), Some(sink)) = (sink_size, sink) {
|
||||
if let Some(expr) = spread.expr() {
|
||||
f(vm, expr, Value::Array(sink.into()))?;
|
||||
}
|
||||
i += sink_size;
|
||||
} else {
|
||||
bail!(pattern.span(), "not enough elements to destructure")
|
||||
}
|
||||
}
|
||||
ast::DestructuringKind::Named(named) => {
|
||||
bail!(named.span(), "cannot destructure named elements from an array")
|
||||
}
|
||||
ast::DestructuringKind::Placeholder(underscore) => {
|
||||
if i < len {
|
||||
i += 1
|
||||
} else {
|
||||
bail!(underscore.span(), "not enough elements to destructure")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if i < len {
|
||||
bail!(pattern.span(), "too many elements to destructure");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn destructure_dict<F>(
|
||||
vm: &mut Vm,
|
||||
dict: Dict,
|
||||
f: F,
|
||||
destruct: ast::Destructuring,
|
||||
) -> SourceResult<()>
|
||||
where
|
||||
F: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<()>,
|
||||
{
|
||||
let mut sink = None;
|
||||
let mut used = HashSet::new();
|
||||
for p in destruct.bindings() {
|
||||
match p {
|
||||
ast::DestructuringKind::Normal(ast::Expr::Ident(ident)) => {
|
||||
let v = dict.get(&ident).at(ident.span())?;
|
||||
f(vm, ast::Expr::Ident(ident), v.clone())?;
|
||||
used.insert(ident.as_str());
|
||||
}
|
||||
ast::DestructuringKind::Sink(spread) => sink = spread.expr(),
|
||||
ast::DestructuringKind::Named(named) => {
|
||||
let name = named.name();
|
||||
let v = dict.get(&name).at(name.span())?;
|
||||
f(vm, named.expr(), v.clone())?;
|
||||
used.insert(name.as_str());
|
||||
}
|
||||
ast::DestructuringKind::Placeholder(_) => {}
|
||||
ast::DestructuringKind::Normal(expr) => {
|
||||
bail!(expr.span(), "expected key, found expression");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(expr) = sink {
|
||||
let mut sink = Dict::new();
|
||||
for (key, value) in dict {
|
||||
if !used.contains(key.as_str()) {
|
||||
sink.insert(key, value);
|
||||
}
|
||||
}
|
||||
f(vm, expr, Value::Dict(sink))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
587
crates/typst/src/eval/call.rs
Normal file
587
crates/typst/src/eval/call.rs
Normal file
@ -0,0 +1,587 @@
|
||||
use comemo::{Prehashed, Tracked, TrackedMut};
|
||||
use ecow::EcoVec;
|
||||
|
||||
use crate::diag::{
|
||||
bail, error, At, DelayedErrors, HintedStrResult, SourceResult, Trace, Tracepoint,
|
||||
};
|
||||
use crate::eval::{Access, Eval, FlowEvent, Route, Tracer, Vm};
|
||||
use crate::foundations::{
|
||||
call_method_mut, is_mutating_method, Arg, Args, Bytes, Closure, Content, Func,
|
||||
IntoValue, NativeElement, Scope, Scopes, Value,
|
||||
};
|
||||
use crate::introspection::{Introspector, Locator};
|
||||
use crate::layout::Vt;
|
||||
use crate::math::{Accent, AccentElem, LrElem};
|
||||
use crate::symbols::Symbol;
|
||||
use crate::syntax::ast::{self, AstNode};
|
||||
use crate::syntax::{Spanned, SyntaxNode};
|
||||
use crate::text::TextElem;
|
||||
use crate::World;
|
||||
|
||||
/// The maxmium function call depth.
|
||||
const MAX_CALL_DEPTH: usize = 64;
|
||||
|
||||
impl Eval for ast::FuncCall<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "FuncCall::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let span = self.span();
|
||||
if vm.depth >= MAX_CALL_DEPTH {
|
||||
bail!(span, "maximum function call depth exceeded");
|
||||
}
|
||||
|
||||
let callee = self.callee();
|
||||
let in_math = in_math(callee);
|
||||
let callee_span = callee.span();
|
||||
let args = self.args();
|
||||
|
||||
// Try to evaluate as a call to an associated function or field.
|
||||
let (callee, mut args) = if let ast::Expr::FieldAccess(access) = callee {
|
||||
let target = access.target();
|
||||
let target_span = target.span();
|
||||
let field = access.field();
|
||||
let field_span = field.span();
|
||||
|
||||
let target = if is_mutating_method(&field) {
|
||||
let mut args = args.eval(vm)?;
|
||||
let target = target.access(vm)?;
|
||||
|
||||
// Only arrays and dictionaries have mutable methods.
|
||||
if matches!(target, Value::Array(_) | Value::Dict(_)) {
|
||||
args.span = span;
|
||||
let point = || Tracepoint::Call(Some(field.get().clone()));
|
||||
return call_method_mut(target, &field, args, span).trace(
|
||||
vm.world(),
|
||||
point,
|
||||
span,
|
||||
);
|
||||
}
|
||||
|
||||
target.clone()
|
||||
} else {
|
||||
access.target().eval(vm)?
|
||||
};
|
||||
|
||||
let mut args = args.eval(vm)?;
|
||||
|
||||
// Handle plugins.
|
||||
if let Value::Plugin(plugin) = &target {
|
||||
let bytes = args.all::<Bytes>()?;
|
||||
args.finish()?;
|
||||
return Ok(plugin.call(&field, bytes).at(span)?.into_value());
|
||||
}
|
||||
|
||||
// Prioritize associated functions on the value's type (i.e.,
|
||||
// methods) over its fields. A function call on a field is only
|
||||
// allowed for functions, types, modules (because they are scopes),
|
||||
// and symbols (because they have modifiers).
|
||||
//
|
||||
// For dictionaries, it is not allowed because it would be ambiguous
|
||||
// (prioritizing associated functions would make an addition of a
|
||||
// new associated function a breaking change and prioritizing fields
|
||||
// would break associated functions for certain dictionaries).
|
||||
if let Some(callee) = target.ty().scope().get(&field) {
|
||||
let this = Arg {
|
||||
span: target_span,
|
||||
name: None,
|
||||
value: Spanned::new(target, target_span),
|
||||
};
|
||||
args.span = span;
|
||||
args.items.insert(0, this);
|
||||
(callee.clone(), args)
|
||||
} else if matches!(
|
||||
target,
|
||||
Value::Symbol(_) | Value::Func(_) | Value::Type(_) | Value::Module(_)
|
||||
) {
|
||||
(target.field(&field).at(field_span)?, args)
|
||||
} else {
|
||||
let mut error = error!(
|
||||
field_span,
|
||||
"type {} has no method `{}`",
|
||||
target.ty(),
|
||||
field.as_str()
|
||||
);
|
||||
|
||||
if let Value::Dict(dict) = target {
|
||||
if matches!(dict.get(&field), Ok(Value::Func(_))) {
|
||||
error.hint(
|
||||
"to call the function stored in the dictionary, \
|
||||
surround the field access with parentheses",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bail!(error);
|
||||
}
|
||||
} else {
|
||||
(callee.eval(vm)?, args.eval(vm)?)
|
||||
};
|
||||
|
||||
// Handle math special cases for non-functions:
|
||||
// Combining accent symbols apply themselves while everything else
|
||||
// simply displays the arguments verbatim.
|
||||
if in_math && !matches!(callee, Value::Func(_)) {
|
||||
if let Value::Symbol(sym) = &callee {
|
||||
let c = sym.get();
|
||||
if let Some(accent) = Symbol::combining_accent(c) {
|
||||
let base = args.expect("base")?;
|
||||
args.finish()?;
|
||||
return Ok(Value::Content(
|
||||
AccentElem::new(base, Accent::new(accent)).pack(),
|
||||
));
|
||||
}
|
||||
}
|
||||
let mut body = Content::empty();
|
||||
for (i, arg) in args.all::<Content>()?.into_iter().enumerate() {
|
||||
if i > 0 {
|
||||
body += TextElem::packed(',');
|
||||
}
|
||||
body += arg;
|
||||
}
|
||||
return Ok(Value::Content(
|
||||
callee.display().spanned(callee_span)
|
||||
+ LrElem::new(TextElem::packed('(') + body + TextElem::packed(')'))
|
||||
.pack(),
|
||||
));
|
||||
}
|
||||
|
||||
let callee = callee.cast::<Func>().at(callee_span)?;
|
||||
let point = || Tracepoint::Call(callee.name().map(Into::into));
|
||||
let f = || callee.call_vm(vm, args).trace(vm.world(), point, span);
|
||||
|
||||
// Stacker is broken on WASM.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
return f();
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
stacker::maybe_grow(32 * 1024, 2 * 1024 * 1024, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Args<'_> {
|
||||
type Output = Args;
|
||||
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let mut items = EcoVec::with_capacity(self.items().count());
|
||||
|
||||
for arg in self.items() {
|
||||
let span = arg.span();
|
||||
match arg {
|
||||
ast::Arg::Pos(expr) => {
|
||||
items.push(Arg {
|
||||
span,
|
||||
name: None,
|
||||
value: Spanned::new(expr.eval(vm)?, expr.span()),
|
||||
});
|
||||
}
|
||||
ast::Arg::Named(named) => {
|
||||
items.push(Arg {
|
||||
span,
|
||||
name: Some(named.name().get().clone().into()),
|
||||
value: Spanned::new(named.expr().eval(vm)?, named.expr().span()),
|
||||
});
|
||||
}
|
||||
ast::Arg::Spread(expr) => match expr.eval(vm)? {
|
||||
Value::None => {}
|
||||
Value::Array(array) => {
|
||||
items.extend(array.into_iter().map(|value| Arg {
|
||||
span,
|
||||
name: None,
|
||||
value: Spanned::new(value, span),
|
||||
}));
|
||||
}
|
||||
Value::Dict(dict) => {
|
||||
items.extend(dict.into_iter().map(|(key, value)| Arg {
|
||||
span,
|
||||
name: Some(key),
|
||||
value: Spanned::new(value, span),
|
||||
}));
|
||||
}
|
||||
Value::Args(args) => items.extend(args.items),
|
||||
v => bail!(expr.span(), "cannot spread {}", v.ty()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Args { span: self.span(), items })
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Closure<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "Closure::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
// Evaluate default values of named parameters.
|
||||
let mut defaults = Vec::new();
|
||||
for param in self.params().children() {
|
||||
if let ast::Param::Named(named) = param {
|
||||
defaults.push(named.expr().eval(vm)?);
|
||||
}
|
||||
}
|
||||
|
||||
// Collect captured variables.
|
||||
let captured = {
|
||||
let mut visitor = CapturesVisitor::new(Some(&vm.scopes));
|
||||
visitor.visit(self.to_untyped());
|
||||
visitor.finish()
|
||||
};
|
||||
|
||||
// Define the closure.
|
||||
let closure = Closure {
|
||||
node: self.to_untyped().clone(),
|
||||
file: vm.file,
|
||||
defaults,
|
||||
captured,
|
||||
};
|
||||
|
||||
Ok(Value::Func(Func::from(closure).spanned(self.params().span())))
|
||||
}
|
||||
}
|
||||
|
||||
/// Call the function in the context with the arguments.
|
||||
#[comemo::memoize]
|
||||
#[tracing::instrument(skip_all)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn call_closure(
|
||||
func: &Func,
|
||||
closure: &Prehashed<Closure>,
|
||||
world: Tracked<dyn World + '_>,
|
||||
route: Tracked<Route>,
|
||||
introspector: Tracked<Introspector>,
|
||||
locator: Tracked<Locator>,
|
||||
delayed: TrackedMut<DelayedErrors>,
|
||||
tracer: TrackedMut<Tracer>,
|
||||
depth: usize,
|
||||
mut args: Args,
|
||||
) -> SourceResult<Value> {
|
||||
let node = closure.node.cast::<ast::Closure>().unwrap();
|
||||
|
||||
// Don't leak the scopes from the call site. Instead, we use the scope
|
||||
// of captured variables we collected earlier.
|
||||
let mut scopes = Scopes::new(None);
|
||||
scopes.top = closure.captured.clone();
|
||||
|
||||
// Prepare VT.
|
||||
let mut locator = Locator::chained(locator);
|
||||
let vt = Vt {
|
||||
world,
|
||||
introspector,
|
||||
locator: &mut locator,
|
||||
delayed,
|
||||
tracer,
|
||||
};
|
||||
|
||||
// Prepare VM.
|
||||
let mut vm = Vm::new(vt, route, closure.file, scopes);
|
||||
vm.depth = depth;
|
||||
|
||||
// Provide the closure itself for recursive calls.
|
||||
if let Some(name) = node.name() {
|
||||
vm.define(name, Value::Func(func.clone()));
|
||||
}
|
||||
|
||||
// Parse the arguments according to the parameter list.
|
||||
let num_pos_params = node
|
||||
.params()
|
||||
.children()
|
||||
.filter(|p| matches!(p, ast::Param::Pos(_)))
|
||||
.count();
|
||||
let num_pos_args = args.to_pos().len();
|
||||
let sink_size = num_pos_args.checked_sub(num_pos_params);
|
||||
|
||||
let mut sink = None;
|
||||
let mut sink_pos_values = None;
|
||||
let mut defaults = closure.defaults.iter();
|
||||
for p in node.params().children() {
|
||||
match p {
|
||||
ast::Param::Pos(pattern) => match pattern {
|
||||
ast::Pattern::Normal(ast::Expr::Ident(ident)) => {
|
||||
vm.define(ident, args.expect::<Value>(&ident)?)
|
||||
}
|
||||
ast::Pattern::Normal(_) => unreachable!(),
|
||||
pattern => {
|
||||
crate::eval::destructure(
|
||||
&mut vm,
|
||||
pattern,
|
||||
args.expect::<Value>("pattern parameter")?,
|
||||
)?;
|
||||
}
|
||||
},
|
||||
ast::Param::Sink(ident) => {
|
||||
sink = ident.name();
|
||||
if let Some(sink_size) = sink_size {
|
||||
sink_pos_values = Some(args.consume(sink_size)?);
|
||||
}
|
||||
}
|
||||
ast::Param::Named(named) => {
|
||||
let name = named.name();
|
||||
let default = defaults.next().unwrap();
|
||||
let value =
|
||||
args.named::<Value>(&name)?.unwrap_or_else(|| default.clone());
|
||||
vm.define(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(sink) = sink {
|
||||
let mut remaining_args = args.take();
|
||||
if let Some(sink_pos_values) = sink_pos_values {
|
||||
remaining_args.items.extend(sink_pos_values);
|
||||
}
|
||||
vm.define(sink, remaining_args);
|
||||
}
|
||||
|
||||
// Ensure all arguments have been used.
|
||||
args.finish()?;
|
||||
|
||||
// Handle control flow.
|
||||
let output = node.body().eval(&mut vm)?;
|
||||
match vm.flow {
|
||||
Some(FlowEvent::Return(_, Some(explicit))) => return Ok(explicit),
|
||||
Some(FlowEvent::Return(_, None)) => {}
|
||||
Some(flow) => bail!(flow.forbidden()),
|
||||
None => {}
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn in_math(expr: ast::Expr) -> bool {
|
||||
match expr {
|
||||
ast::Expr::MathIdent(_) => true,
|
||||
ast::Expr::FieldAccess(access) => in_math(access.target()),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// A visitor that determines which variables to capture for a closure.
|
||||
pub struct CapturesVisitor<'a> {
|
||||
external: Option<&'a Scopes<'a>>,
|
||||
internal: Scopes<'a>,
|
||||
captures: Scope,
|
||||
}
|
||||
|
||||
impl<'a> CapturesVisitor<'a> {
|
||||
/// Create a new visitor for the given external scopes.
|
||||
pub fn new(external: Option<&'a Scopes<'a>>) -> Self {
|
||||
Self {
|
||||
external,
|
||||
internal: Scopes::new(None),
|
||||
captures: Scope::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the scope of captured variables.
|
||||
pub fn finish(self) -> Scope {
|
||||
self.captures
|
||||
}
|
||||
|
||||
/// Visit any node and collect all captured variables.
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn visit(&mut self, node: &SyntaxNode) {
|
||||
match node.cast() {
|
||||
// Every identifier is a potential variable that we need to capture.
|
||||
// Identifiers that shouldn't count as captures because they
|
||||
// actually bind a new name are handled below (individually through
|
||||
// the expressions that contain them).
|
||||
Some(ast::Expr::Ident(ident)) => self.capture(&ident, Scopes::get),
|
||||
Some(ast::Expr::MathIdent(ident)) => {
|
||||
self.capture(&ident, Scopes::get_in_math)
|
||||
}
|
||||
|
||||
// Code and content blocks create a scope.
|
||||
Some(ast::Expr::Code(_) | ast::Expr::Content(_)) => {
|
||||
self.internal.enter();
|
||||
for child in node.children() {
|
||||
self.visit(child);
|
||||
}
|
||||
self.internal.exit();
|
||||
}
|
||||
|
||||
// Don't capture the field of a field access.
|
||||
Some(ast::Expr::FieldAccess(access)) => {
|
||||
self.visit(access.target().to_untyped());
|
||||
}
|
||||
|
||||
// A closure contains parameter bindings, which are bound before the
|
||||
// body is evaluated. Care must be taken so that the default values
|
||||
// of named parameters cannot access previous parameter bindings.
|
||||
Some(ast::Expr::Closure(expr)) => {
|
||||
for param in expr.params().children() {
|
||||
if let ast::Param::Named(named) = param {
|
||||
self.visit(named.expr().to_untyped());
|
||||
}
|
||||
}
|
||||
|
||||
self.internal.enter();
|
||||
if let Some(name) = expr.name() {
|
||||
self.bind(name);
|
||||
}
|
||||
|
||||
for param in expr.params().children() {
|
||||
match param {
|
||||
ast::Param::Pos(pattern) => {
|
||||
for ident in pattern.idents() {
|
||||
self.bind(ident);
|
||||
}
|
||||
}
|
||||
ast::Param::Named(named) => self.bind(named.name()),
|
||||
ast::Param::Sink(spread) => {
|
||||
self.bind(spread.name().unwrap_or_default())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.visit(expr.body().to_untyped());
|
||||
self.internal.exit();
|
||||
}
|
||||
|
||||
// A let expression contains a binding, but that binding is only
|
||||
// active after the body is evaluated.
|
||||
Some(ast::Expr::Let(expr)) => {
|
||||
if let Some(init) = expr.init() {
|
||||
self.visit(init.to_untyped());
|
||||
}
|
||||
|
||||
for ident in expr.kind().idents() {
|
||||
self.bind(ident);
|
||||
}
|
||||
}
|
||||
|
||||
// A for loop contains one or two bindings in its pattern. These are
|
||||
// active after the iterable is evaluated but before the body is
|
||||
// evaluated.
|
||||
Some(ast::Expr::For(expr)) => {
|
||||
self.visit(expr.iter().to_untyped());
|
||||
self.internal.enter();
|
||||
|
||||
let pattern = expr.pattern();
|
||||
for ident in pattern.idents() {
|
||||
self.bind(ident);
|
||||
}
|
||||
|
||||
self.visit(expr.body().to_untyped());
|
||||
self.internal.exit();
|
||||
}
|
||||
|
||||
// An import contains items, but these are active only after the
|
||||
// path is evaluated.
|
||||
Some(ast::Expr::Import(expr)) => {
|
||||
self.visit(expr.source().to_untyped());
|
||||
if let Some(ast::Imports::Items(items)) = expr.imports() {
|
||||
for item in items.iter() {
|
||||
self.bind(item.bound_name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
// Never capture the name part of a named pair.
|
||||
if let Some(named) = node.cast::<ast::Named>() {
|
||||
self.visit(named.expr().to_untyped());
|
||||
return;
|
||||
}
|
||||
|
||||
// Everything else is traversed from left to right.
|
||||
for child in node.children() {
|
||||
self.visit(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Bind a new internal variable.
|
||||
fn bind(&mut self, ident: ast::Ident) {
|
||||
self.internal.top.define(ident.get().clone(), Value::None);
|
||||
}
|
||||
|
||||
/// Capture a variable if it isn't internal.
|
||||
#[inline]
|
||||
fn capture(
|
||||
&mut self,
|
||||
ident: &str,
|
||||
getter: impl FnOnce(&'a Scopes<'a>, &str) -> HintedStrResult<&'a Value>,
|
||||
) {
|
||||
if self.internal.get(ident).is_err() {
|
||||
let Some(value) = self
|
||||
.external
|
||||
.map(|external| getter(external, ident).ok())
|
||||
.unwrap_or(Some(&Value::None))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.captures.define_captured(ident, value.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::syntax::parse;
|
||||
|
||||
#[track_caller]
|
||||
fn test(text: &str, result: &[&str]) {
|
||||
let mut scopes = Scopes::new(None);
|
||||
scopes.top.define("f", 0);
|
||||
scopes.top.define("x", 0);
|
||||
scopes.top.define("y", 0);
|
||||
scopes.top.define("z", 0);
|
||||
|
||||
let mut visitor = CapturesVisitor::new(Some(&scopes));
|
||||
let root = parse(text);
|
||||
visitor.visit(&root);
|
||||
|
||||
let captures = visitor.finish();
|
||||
let mut names: Vec<_> = captures.iter().map(|(k, _)| k).collect();
|
||||
names.sort();
|
||||
|
||||
assert_eq!(names, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_captures() {
|
||||
// Let binding and function definition.
|
||||
test("#let x = x", &["x"]);
|
||||
test("#let x; #(x + y)", &["y"]);
|
||||
test("#let f(x, y) = x + y", &[]);
|
||||
test("#let f(x, y) = f", &[]);
|
||||
test("#let f = (x, y) => f", &["f"]);
|
||||
|
||||
// Closure with different kinds of params.
|
||||
test("#((x, y) => x + z)", &["z"]);
|
||||
test("#((x: y, z) => x + z)", &["y"]);
|
||||
test("#((..x) => x + y)", &["y"]);
|
||||
test("#((x, y: x + z) => x + y)", &["x", "z"]);
|
||||
test("#{x => x; x}", &["x"]);
|
||||
|
||||
// Show rule.
|
||||
test("#show y: x => x", &["y"]);
|
||||
test("#show y: x => x + z", &["y", "z"]);
|
||||
test("#show x: x => x", &["x"]);
|
||||
|
||||
// For loop.
|
||||
test("#for x in y { x + z }", &["y", "z"]);
|
||||
test("#for (x, y) in y { x + y }", &["y"]);
|
||||
test("#for x in y {} #x", &["x", "y"]);
|
||||
|
||||
// Import.
|
||||
test("#import z: x, y", &["z"]);
|
||||
test("#import x + y: x, y, z", &["x", "y"]);
|
||||
|
||||
// Blocks.
|
||||
test("#{ let x = 1; { let y = 2; y }; x + y }", &["y"]);
|
||||
test("#[#let x = 1]#x", &["x"]);
|
||||
|
||||
// Field access.
|
||||
test("#foo(body: 1)", &[]);
|
||||
test("#(body: 1)", &[]);
|
||||
test("#(body = 1)", &[]);
|
||||
test("#(body += y)", &["y"]);
|
||||
test("#{ (body, a) = (y, 1) }", &["y"]);
|
||||
test("#(x.at(y) = 5)", &["x", "y"])
|
||||
}
|
||||
}
|
317
crates/typst/src/eval/code.rs
Normal file
317
crates/typst/src/eval/code.rs
Normal file
@ -0,0 +1,317 @@
|
||||
use ecow::{eco_vec, EcoVec};
|
||||
|
||||
use crate::diag::{bail, error, At, SourceDiagnostic, SourceResult};
|
||||
use crate::eval::{ops, Eval, Vm};
|
||||
use crate::foundations::{Array, Content, Dict, Str, Value};
|
||||
use crate::syntax::ast::{self, AstNode};
|
||||
|
||||
impl Eval for ast::Code<'_> {
|
||||
type Output = Value;
|
||||
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
eval_code(vm, &mut self.exprs())
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate a stream of expressions.
|
||||
fn eval_code<'a>(
|
||||
vm: &mut Vm,
|
||||
exprs: &mut impl Iterator<Item = ast::Expr<'a>>,
|
||||
) -> SourceResult<Value> {
|
||||
let flow = vm.flow.take();
|
||||
let mut output = Value::None;
|
||||
|
||||
while let Some(expr) = exprs.next() {
|
||||
let span = expr.span();
|
||||
let value = match expr {
|
||||
ast::Expr::Set(set) => {
|
||||
let styles = set.eval(vm)?;
|
||||
if vm.flow.is_some() {
|
||||
break;
|
||||
}
|
||||
|
||||
let tail = eval_code(vm, exprs)?.display();
|
||||
Value::Content(tail.styled_with_map(styles))
|
||||
}
|
||||
ast::Expr::Show(show) => {
|
||||
let recipe = show.eval(vm)?;
|
||||
if vm.flow.is_some() {
|
||||
break;
|
||||
}
|
||||
|
||||
let tail = eval_code(vm, exprs)?.display();
|
||||
Value::Content(tail.styled_with_recipe(vm, recipe)?)
|
||||
}
|
||||
_ => expr.eval(vm)?,
|
||||
};
|
||||
|
||||
output = ops::join(output, value).at(span)?;
|
||||
|
||||
if vm.flow.is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if flow.is_some() {
|
||||
vm.flow = flow;
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
impl Eval for ast::Expr<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "Expr::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let span = self.span();
|
||||
let forbidden = |name| {
|
||||
error!(span, "{} is only allowed directly in code and content blocks", name)
|
||||
};
|
||||
|
||||
let v = match self {
|
||||
Self::Text(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Space(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Linebreak(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Parbreak(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Escape(v) => v.eval(vm),
|
||||
Self::Shorthand(v) => v.eval(vm),
|
||||
Self::SmartQuote(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Strong(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Emph(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Raw(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Link(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Label(v) => v.eval(vm),
|
||||
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::Equation(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Math(v) => v.eval(vm).map(Value::Content),
|
||||
Self::MathIdent(v) => v.eval(vm),
|
||||
Self::MathAlignPoint(v) => v.eval(vm).map(Value::Content),
|
||||
Self::MathDelimited(v) => v.eval(vm).map(Value::Content),
|
||||
Self::MathAttach(v) => v.eval(vm).map(Value::Content),
|
||||
Self::MathPrimes(v) => v.eval(vm).map(Value::Content),
|
||||
Self::MathFrac(v) => v.eval(vm).map(Value::Content),
|
||||
Self::MathRoot(v) => v.eval(vm).map(Value::Content),
|
||||
Self::Ident(v) => v.eval(vm),
|
||||
Self::None(v) => v.eval(vm),
|
||||
Self::Auto(v) => v.eval(vm),
|
||||
Self::Bool(v) => v.eval(vm),
|
||||
Self::Int(v) => v.eval(vm),
|
||||
Self::Float(v) => v.eval(vm),
|
||||
Self::Numeric(v) => v.eval(vm),
|
||||
Self::Str(v) => v.eval(vm),
|
||||
Self::Code(v) => v.eval(vm),
|
||||
Self::Content(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::Closure(v) => v.eval(vm),
|
||||
Self::Unary(v) => v.eval(vm),
|
||||
Self::Binary(v) => v.eval(vm),
|
||||
Self::Let(v) => v.eval(vm),
|
||||
Self::DestructAssign(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),
|
||||
}?
|
||||
.spanned(span);
|
||||
|
||||
if vm.inspected == Some(span) {
|
||||
vm.vt.tracer.value(v.clone());
|
||||
}
|
||||
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Ident<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "Ident::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
vm.scopes.get(&self).cloned().at(self.span())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::None<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "None::eval", skip_all)]
|
||||
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||
Ok(Value::None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Auto<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "Auto::eval", skip_all)]
|
||||
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||
Ok(Value::Auto)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Bool<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "Bool::eval", skip_all)]
|
||||
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||
Ok(Value::Bool(self.get()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Int<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "Int::eval", skip_all)]
|
||||
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||
Ok(Value::Int(self.get()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Float<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "Float::eval", skip_all)]
|
||||
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||
Ok(Value::Float(self.get()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Numeric<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "Numeric::eval", skip_all)]
|
||||
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||
Ok(Value::numeric(self.get()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Str<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "Str::eval", skip_all)]
|
||||
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||
Ok(Value::Str(self.get().into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Array<'_> {
|
||||
type Output = Array;
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let items = self.items();
|
||||
|
||||
let mut vec = EcoVec::with_capacity(items.size_hint().0);
|
||||
for item in items {
|
||||
match item {
|
||||
ast::ArrayItem::Pos(expr) => vec.push(expr.eval(vm)?),
|
||||
ast::ArrayItem::Spread(expr) => match expr.eval(vm)? {
|
||||
Value::None => {}
|
||||
Value::Array(array) => vec.extend(array.into_iter()),
|
||||
v => bail!(expr.span(), "cannot spread {} into array", v.ty()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(vec.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Dict<'_> {
|
||||
type Output = Dict;
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let mut map = indexmap::IndexMap::new();
|
||||
|
||||
let mut invalid_keys = eco_vec![];
|
||||
|
||||
for item in self.items() {
|
||||
match item {
|
||||
ast::DictItem::Named(named) => {
|
||||
map.insert(named.name().get().clone().into(), named.expr().eval(vm)?);
|
||||
}
|
||||
ast::DictItem::Keyed(keyed) => {
|
||||
let raw_key = keyed.key();
|
||||
let key = raw_key.eval(vm)?;
|
||||
let key = key.cast::<Str>().unwrap_or_else(|error| {
|
||||
let error = SourceDiagnostic::error(raw_key.span(), error);
|
||||
invalid_keys.push(error);
|
||||
Str::default()
|
||||
});
|
||||
map.insert(key, keyed.expr().eval(vm)?);
|
||||
}
|
||||
ast::DictItem::Spread(expr) => match expr.eval(vm)? {
|
||||
Value::None => {}
|
||||
Value::Dict(dict) => map.extend(dict.into_iter()),
|
||||
v => bail!(expr.span(), "cannot spread {} into dictionary", v.ty()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if !invalid_keys.is_empty() {
|
||||
return Err(invalid_keys);
|
||||
}
|
||||
|
||||
Ok(map.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::CodeBlock<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "CodeBlock::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
vm.scopes.enter();
|
||||
let output = self.body().eval(vm)?;
|
||||
vm.scopes.exit();
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::ContentBlock<'_> {
|
||||
type Output = Content;
|
||||
|
||||
#[tracing::instrument(name = "ContentBlock::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
vm.scopes.enter();
|
||||
let content = self.body().eval(vm)?;
|
||||
vm.scopes.exit();
|
||||
Ok(content)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Parenthesized<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "Parenthesized::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
self.expr().eval(vm)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::FieldAccess<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "FieldAccess::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let value = self.target().eval(vm)?;
|
||||
let field = self.field();
|
||||
value.field(&field).at(field.span())
|
||||
}
|
||||
}
|
227
crates/typst/src/eval/flow.rs
Normal file
227
crates/typst/src/eval/flow.rs
Normal file
@ -0,0 +1,227 @@
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::diag::{bail, error, At, SourceDiagnostic, SourceResult};
|
||||
use crate::eval::{destructure, ops, Eval, Vm};
|
||||
use crate::foundations::{IntoValue, Value};
|
||||
use crate::syntax::ast::{self, AstNode};
|
||||
use crate::syntax::{Span, SyntaxKind, SyntaxNode};
|
||||
|
||||
/// The maximum number of loop iterations.
|
||||
const MAX_ITERATIONS: usize = 10_000;
|
||||
|
||||
/// A control flow event that occurred during evaluation.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) enum FlowEvent {
|
||||
/// Stop iteration in a loop.
|
||||
Break(Span),
|
||||
/// Skip the remainder of the current iteration in a loop.
|
||||
Continue(Span),
|
||||
/// Stop execution of a function early, optionally returning an explicit
|
||||
/// value.
|
||||
Return(Span, Option<Value>),
|
||||
}
|
||||
|
||||
impl FlowEvent {
|
||||
/// Return an error stating that this control flow is forbidden.
|
||||
pub fn forbidden(&self) -> SourceDiagnostic {
|
||||
match *self {
|
||||
Self::Break(span) => {
|
||||
error!(span, "cannot break outside of loop")
|
||||
}
|
||||
Self::Continue(span) => {
|
||||
error!(span, "cannot continue outside of loop")
|
||||
}
|
||||
Self::Return(span, _) => {
|
||||
error!(span, "cannot return outside of function")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Conditional<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "Conditional::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let condition = self.condition();
|
||||
if condition.eval(vm)?.cast::<bool>().at(condition.span())? {
|
||||
self.if_body().eval(vm)
|
||||
} else if let Some(else_body) = self.else_body() {
|
||||
else_body.eval(vm)
|
||||
} else {
|
||||
Ok(Value::None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::WhileLoop<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "WhileLoop::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let flow = vm.flow.take();
|
||||
let mut output = Value::None;
|
||||
let mut i = 0;
|
||||
|
||||
let condition = self.condition();
|
||||
let body = self.body();
|
||||
|
||||
while condition.eval(vm)?.cast::<bool>().at(condition.span())? {
|
||||
if i == 0
|
||||
&& is_invariant(condition.to_untyped())
|
||||
&& !can_diverge(body.to_untyped())
|
||||
{
|
||||
bail!(condition.span(), "condition is always true");
|
||||
} else if i >= MAX_ITERATIONS {
|
||||
bail!(self.span(), "loop seems to be infinite");
|
||||
}
|
||||
|
||||
let value = body.eval(vm)?;
|
||||
output = ops::join(output, value).at(body.span())?;
|
||||
|
||||
match vm.flow {
|
||||
Some(FlowEvent::Break(_)) => {
|
||||
vm.flow = None;
|
||||
break;
|
||||
}
|
||||
Some(FlowEvent::Continue(_)) => vm.flow = None,
|
||||
Some(FlowEvent::Return(..)) => break,
|
||||
None => {}
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if flow.is_some() {
|
||||
vm.flow = flow;
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::ForLoop<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "ForLoop::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let flow = vm.flow.take();
|
||||
let mut output = Value::None;
|
||||
|
||||
macro_rules! iter {
|
||||
(for $pat:ident in $iter:expr) => {{
|
||||
vm.scopes.enter();
|
||||
|
||||
#[allow(unused_parens)]
|
||||
for value in $iter {
|
||||
destructure(vm, $pat, value.into_value())?;
|
||||
|
||||
let body = self.body();
|
||||
let value = body.eval(vm)?;
|
||||
output = ops::join(output, value).at(body.span())?;
|
||||
|
||||
match vm.flow {
|
||||
Some(FlowEvent::Break(_)) => {
|
||||
vm.flow = None;
|
||||
break;
|
||||
}
|
||||
Some(FlowEvent::Continue(_)) => vm.flow = None,
|
||||
Some(FlowEvent::Return(..)) => break,
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
vm.scopes.exit();
|
||||
}};
|
||||
}
|
||||
|
||||
let iter = self.iter().eval(vm)?;
|
||||
let pattern = self.pattern();
|
||||
|
||||
match (&pattern, iter.clone()) {
|
||||
(ast::Pattern::Normal(_), Value::Str(string)) => {
|
||||
// Iterate over graphemes of string.
|
||||
iter!(for pattern in string.as_str().graphemes(true));
|
||||
}
|
||||
(_, Value::Dict(dict)) => {
|
||||
// Iterate over pairs of dict.
|
||||
iter!(for pattern in dict.pairs());
|
||||
}
|
||||
(_, Value::Array(array)) => {
|
||||
// Iterate over values of array.
|
||||
iter!(for pattern in array);
|
||||
}
|
||||
(ast::Pattern::Normal(_), _) => {
|
||||
bail!(self.iter().span(), "cannot loop over {}", iter.ty());
|
||||
}
|
||||
(_, _) => {
|
||||
bail!(pattern.span(), "cannot destructure values of {}", iter.ty())
|
||||
}
|
||||
}
|
||||
|
||||
if flow.is_some() {
|
||||
vm.flow = flow;
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::LoopBreak<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "LoopBreak::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
if vm.flow.is_none() {
|
||||
vm.flow = Some(FlowEvent::Break(self.span()));
|
||||
}
|
||||
Ok(Value::None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::LoopContinue<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "LoopContinue::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
if vm.flow.is_none() {
|
||||
vm.flow = Some(FlowEvent::Continue(self.span()));
|
||||
}
|
||||
Ok(Value::None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::FuncReturn<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "FuncReturn::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let value = self.body().map(|body| body.eval(vm)).transpose()?;
|
||||
if vm.flow.is_none() {
|
||||
vm.flow = Some(FlowEvent::Return(self.span(), value));
|
||||
}
|
||||
Ok(Value::None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the expression always evaluates to the same value.
|
||||
fn is_invariant(expr: &SyntaxNode) -> bool {
|
||||
match expr.cast() {
|
||||
Some(ast::Expr::Ident(_)) => false,
|
||||
Some(ast::Expr::MathIdent(_)) => false,
|
||||
Some(ast::Expr::FieldAccess(access)) => {
|
||||
is_invariant(access.target().to_untyped())
|
||||
}
|
||||
Some(ast::Expr::FuncCall(call)) => {
|
||||
is_invariant(call.callee().to_untyped())
|
||||
&& is_invariant(call.args().to_untyped())
|
||||
}
|
||||
_ => expr.children().all(is_invariant),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the expression contains a break or return.
|
||||
fn can_diverge(expr: &SyntaxNode) -> bool {
|
||||
matches!(expr.kind(), SyntaxKind::Break | SyntaxKind::Return)
|
||||
|| expr.children().any(can_diverge)
|
||||
}
|
227
crates/typst/src/eval/import.rs
Normal file
227
crates/typst/src/eval/import.rs
Normal file
@ -0,0 +1,227 @@
|
||||
use comemo::TrackedMut;
|
||||
use ecow::{eco_format, eco_vec, EcoString};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::diag::{
|
||||
bail, error, warning, At, FileError, SourceResult, StrResult, Trace, Tracepoint,
|
||||
};
|
||||
use crate::eval::{eval, Eval, Vm};
|
||||
use crate::foundations::{Content, Module, Value};
|
||||
use crate::syntax::ast::{self, AstNode};
|
||||
use crate::syntax::{FileId, PackageSpec, PackageVersion, Span, VirtualPath};
|
||||
use crate::World;
|
||||
|
||||
impl Eval for ast::ModuleImport<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "ModuleImport::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let source = self.source();
|
||||
let source_span = source.span();
|
||||
let mut source = source.eval(vm)?;
|
||||
let new_name = self.new_name();
|
||||
let imports = self.imports();
|
||||
|
||||
match &source {
|
||||
Value::Func(func) => {
|
||||
if func.scope().is_none() {
|
||||
bail!(source_span, "cannot import from user-defined functions");
|
||||
}
|
||||
}
|
||||
Value::Type(_) => {}
|
||||
other => {
|
||||
source = Value::Module(import(vm, other.clone(), source_span, true)?);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(new_name) = &new_name {
|
||||
if let ast::Expr::Ident(ident) = self.source() {
|
||||
if ident.as_str() == new_name.as_str() {
|
||||
// Warn on `import x as x`
|
||||
vm.vt.tracer.warn(warning!(
|
||||
new_name.span(),
|
||||
"unnecessary import rename to same name",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Define renamed module on the scope.
|
||||
vm.scopes.top.define(new_name.as_str(), source.clone());
|
||||
}
|
||||
|
||||
let scope = source.scope().unwrap();
|
||||
match imports {
|
||||
None => {
|
||||
// Only import here if there is no rename.
|
||||
if new_name.is_none() {
|
||||
let name: EcoString = source.name().unwrap().into();
|
||||
vm.scopes.top.define(name, source);
|
||||
}
|
||||
}
|
||||
Some(ast::Imports::Wildcard) => {
|
||||
for (var, value) in scope.iter() {
|
||||
vm.scopes.top.define(var.clone(), value.clone());
|
||||
}
|
||||
}
|
||||
Some(ast::Imports::Items(items)) => {
|
||||
let mut errors = eco_vec![];
|
||||
for item in items.iter() {
|
||||
let original_ident = item.original_name();
|
||||
if let Some(value) = scope.get(&original_ident) {
|
||||
// Warn on `import ...: x as x`
|
||||
if let ast::ImportItem::Renamed(renamed_item) = &item {
|
||||
if renamed_item.original_name().as_str()
|
||||
== renamed_item.new_name().as_str()
|
||||
{
|
||||
vm.vt.tracer.warn(warning!(
|
||||
renamed_item.new_name().span(),
|
||||
"unnecessary import rename to same name",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
vm.define(item.bound_name(), value.clone());
|
||||
} else {
|
||||
errors.push(error!(original_ident.span(), "unresolved import"));
|
||||
}
|
||||
}
|
||||
if !errors.is_empty() {
|
||||
return Err(errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::ModuleInclude<'_> {
|
||||
type Output = Content;
|
||||
|
||||
#[tracing::instrument(name = "ModuleInclude::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let span = self.source().span();
|
||||
let source = self.source().eval(vm)?;
|
||||
let module = import(vm, source, span, false)?;
|
||||
Ok(module.content())
|
||||
}
|
||||
}
|
||||
|
||||
/// Process an import of a module relative to the current location.
|
||||
pub fn import(
|
||||
vm: &mut Vm,
|
||||
source: Value,
|
||||
span: Span,
|
||||
allow_scopes: bool,
|
||||
) -> SourceResult<Module> {
|
||||
let path = match source {
|
||||
Value::Str(path) => path,
|
||||
Value::Module(module) => return Ok(module),
|
||||
v if allow_scopes => {
|
||||
bail!(span, "expected path, module, function, or type, found {}", v.ty())
|
||||
}
|
||||
v => bail!(span, "expected path or module, found {}", v.ty()),
|
||||
};
|
||||
|
||||
// Handle package and file imports.
|
||||
let path = path.as_str();
|
||||
if path.starts_with('@') {
|
||||
let spec = path.parse::<PackageSpec>().at(span)?;
|
||||
import_package(vm, spec, span)
|
||||
} else {
|
||||
import_file(vm, path, span)
|
||||
}
|
||||
}
|
||||
|
||||
/// Import an external package.
|
||||
fn import_package(vm: &mut Vm, spec: PackageSpec, span: Span) -> SourceResult<Module> {
|
||||
// Evaluate the manifest.
|
||||
let manifest_id = FileId::new(Some(spec.clone()), VirtualPath::new("typst.toml"));
|
||||
let bytes = vm.world().file(manifest_id).at(span)?;
|
||||
let manifest = PackageManifest::parse(&bytes).at(span)?;
|
||||
manifest.validate(&spec).at(span)?;
|
||||
|
||||
// Evaluate the entry point.
|
||||
let entrypoint_id = manifest_id.join(&manifest.package.entrypoint);
|
||||
let source = vm.world().source(entrypoint_id).at(span)?;
|
||||
let point = || Tracepoint::Import;
|
||||
Ok(eval(vm.world(), vm.route, TrackedMut::reborrow_mut(&mut vm.vt.tracer), &source)
|
||||
.trace(vm.world(), point, span)?
|
||||
.with_name(manifest.package.name))
|
||||
}
|
||||
|
||||
/// Import a file from a path.
|
||||
fn import_file(vm: &mut Vm, path: &str, span: Span) -> SourceResult<Module> {
|
||||
// Load the source file.
|
||||
let world = vm.world();
|
||||
let id = vm.resolve_path(path).at(span)?;
|
||||
let source = world.source(id).at(span)?;
|
||||
|
||||
// Prevent cyclic importing.
|
||||
if vm.route.contains(source.id()) {
|
||||
bail!(span, "cyclic import");
|
||||
}
|
||||
|
||||
// Evaluate the file.
|
||||
let point = || Tracepoint::Import;
|
||||
eval(world, vm.route, TrackedMut::reborrow_mut(&mut vm.vt.tracer), &source)
|
||||
.trace(world, point, span)
|
||||
}
|
||||
|
||||
/// A parsed package manifest.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||
struct PackageManifest {
|
||||
/// Details about the package itself.
|
||||
package: PackageInfo,
|
||||
}
|
||||
|
||||
/// The `package` key in the manifest.
|
||||
///
|
||||
/// More fields are specified, but they are not relevant to the compiler.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||
struct PackageInfo {
|
||||
/// The name of the package within its namespace.
|
||||
name: EcoString,
|
||||
/// The package's version.
|
||||
version: PackageVersion,
|
||||
/// The path of the entrypoint into the package.
|
||||
entrypoint: EcoString,
|
||||
/// The minimum required compiler version for the package.
|
||||
compiler: Option<PackageVersion>,
|
||||
}
|
||||
|
||||
impl PackageManifest {
|
||||
/// Parse the manifest from raw bytes.
|
||||
fn parse(bytes: &[u8]) -> StrResult<Self> {
|
||||
let string = std::str::from_utf8(bytes).map_err(FileError::from)?;
|
||||
toml::from_str(string).map_err(|err| {
|
||||
eco_format!("package manifest is malformed: {}", err.message())
|
||||
})
|
||||
}
|
||||
|
||||
/// Ensure that this manifest is indeed for the specified package.
|
||||
fn validate(&self, spec: &PackageSpec) -> StrResult<()> {
|
||||
if self.package.name != spec.name {
|
||||
bail!("package manifest contains mismatched name `{}`", self.package.name);
|
||||
}
|
||||
|
||||
if self.package.version != spec.version {
|
||||
bail!(
|
||||
"package manifest contains mismatched version {}",
|
||||
self.package.version
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(compiler) = self.package.compiler {
|
||||
let current = PackageVersion::compiler();
|
||||
if current < compiler {
|
||||
bail!(
|
||||
"package requires typst {compiler} or newer \
|
||||
(current version is {current})"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,179 +0,0 @@
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use comemo::Tracked;
|
||||
use ecow::EcoString;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use crate::diag::SourceResult;
|
||||
use crate::doc::Document;
|
||||
use crate::eval::Module;
|
||||
use crate::geom::{Abs, Dir};
|
||||
use crate::model::{Content, Element, Introspector, Label, StyleChain, Styles, Vt};
|
||||
use crate::util::hash128;
|
||||
|
||||
/// Definition of Typst's standard library.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct Library {
|
||||
/// The scope containing definitions that are available everywhere.
|
||||
pub global: Module,
|
||||
/// The scope containing definitions available in math mode.
|
||||
pub math: Module,
|
||||
/// The default properties for page size, font selection and so on.
|
||||
pub styles: Styles,
|
||||
/// Defines which standard library items fulfill which syntactical roles.
|
||||
pub items: LangItems,
|
||||
}
|
||||
|
||||
/// Definition of library items the language is aware of.
|
||||
#[derive(Clone)]
|
||||
pub struct LangItems {
|
||||
/// The root layout function.
|
||||
pub layout:
|
||||
fn(vt: &mut Vt, content: &Content, styles: StyleChain) -> SourceResult<Document>,
|
||||
/// Access the em size.
|
||||
pub em: fn(StyleChain) -> Abs,
|
||||
/// Access the text direction.
|
||||
pub dir: fn(StyleChain) -> Dir,
|
||||
/// Whitespace.
|
||||
pub space: fn() -> Content,
|
||||
/// A forced line break: `\`.
|
||||
pub linebreak: fn() -> Content,
|
||||
/// Plain text without markup.
|
||||
pub text: fn(text: EcoString) -> Content,
|
||||
/// The text element.
|
||||
pub text_elem: Element,
|
||||
/// Get the string if this is a text element.
|
||||
pub text_str: fn(&Content) -> Option<&EcoString>,
|
||||
/// A smart quote: `'` or `"`.
|
||||
pub smart_quote: fn(double: bool) -> Content,
|
||||
/// A paragraph break.
|
||||
pub parbreak: fn() -> Content,
|
||||
/// Strong content: `*Strong*`.
|
||||
pub strong: fn(body: Content) -> Content,
|
||||
/// Emphasized content: `_Emphasized_`.
|
||||
pub emph: fn(body: Content) -> Content,
|
||||
/// Raw text with optional syntax highlighting: `` `...` ``.
|
||||
pub raw: fn(text: EcoString, tag: Option<EcoString>, block: bool) -> Content,
|
||||
/// The language names and tags supported by raw text.
|
||||
pub raw_languages: fn() -> Vec<(&'static str, Vec<&'static str>)>,
|
||||
/// A hyperlink: `https://typst.org`.
|
||||
pub link: fn(url: EcoString) -> Content,
|
||||
/// A reference: `@target`, `@target[..]`.
|
||||
pub reference: fn(target: Label, supplement: Option<Content>) -> Content,
|
||||
/// The keys contained in the bibliography and short descriptions of them.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub bibliography_keys:
|
||||
fn(introspector: Tracked<Introspector>) -> Vec<(EcoString, Option<EcoString>)>,
|
||||
/// A section heading: `= Introduction`.
|
||||
pub heading: fn(level: NonZeroUsize, body: Content) -> Content,
|
||||
/// The heading element.
|
||||
pub heading_elem: Element,
|
||||
/// An item in a bullet list: `- ...`.
|
||||
pub list_item: fn(body: Content) -> Content,
|
||||
/// An item in an enumeration (numbered list): `+ ...` or `1. ...`.
|
||||
pub enum_item: fn(number: Option<usize>, body: Content) -> Content,
|
||||
/// An item in a term list: `/ Term: Details`.
|
||||
pub term_item: fn(term: Content, description: Content) -> Content,
|
||||
/// A mathematical equation: `$x$`, `$ x^2 $`.
|
||||
pub equation: fn(body: Content, block: bool) -> Content,
|
||||
/// An alignment point in math: `&`.
|
||||
pub math_align_point: fn() -> Content,
|
||||
/// Matched delimiters in math: `[x + y]`.
|
||||
pub math_delimited: fn(open: Content, body: Content, close: Content) -> Content,
|
||||
/// A base with optional attachments in math: `a_1^2`.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub math_attach: fn(
|
||||
base: Content,
|
||||
// Positioned smartly.
|
||||
t: Option<Content>,
|
||||
b: Option<Content>,
|
||||
// Fixed positions.
|
||||
tl: Option<Content>,
|
||||
bl: Option<Content>,
|
||||
tr: Option<Content>,
|
||||
br: Option<Content>,
|
||||
) -> Content,
|
||||
/// Grouped primes: `a'''`.
|
||||
pub math_primes: fn(count: usize) -> Content,
|
||||
/// A base with an accent: `arrow(x)`.
|
||||
pub math_accent: fn(base: Content, accent: char) -> Content,
|
||||
/// A fraction in math: `x/2`.
|
||||
pub math_frac: fn(num: Content, denom: Content) -> Content,
|
||||
/// A root in math: `√x`, `∛x` or `∜x`.
|
||||
pub math_root: fn(index: Option<Content>, radicand: Content) -> Content,
|
||||
}
|
||||
|
||||
impl Debug for LangItems {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad("LangItems { .. }")
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for LangItems {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
(self.layout as usize).hash(state);
|
||||
(self.em as usize).hash(state);
|
||||
(self.dir as usize).hash(state);
|
||||
self.space.hash(state);
|
||||
self.linebreak.hash(state);
|
||||
self.text.hash(state);
|
||||
self.text_elem.hash(state);
|
||||
(self.text_str as usize).hash(state);
|
||||
self.smart_quote.hash(state);
|
||||
self.parbreak.hash(state);
|
||||
self.strong.hash(state);
|
||||
self.emph.hash(state);
|
||||
self.raw.hash(state);
|
||||
self.raw_languages.hash(state);
|
||||
self.link.hash(state);
|
||||
self.reference.hash(state);
|
||||
(self.bibliography_keys as usize).hash(state);
|
||||
self.heading.hash(state);
|
||||
self.heading_elem.hash(state);
|
||||
self.list_item.hash(state);
|
||||
self.enum_item.hash(state);
|
||||
self.term_item.hash(state);
|
||||
self.equation.hash(state);
|
||||
self.math_align_point.hash(state);
|
||||
self.math_delimited.hash(state);
|
||||
self.math_attach.hash(state);
|
||||
self.math_accent.hash(state);
|
||||
self.math_frac.hash(state);
|
||||
self.math_root.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// Global storage for lang items.
|
||||
#[doc(hidden)]
|
||||
pub static LANG_ITEMS: OnceLock<LangItems> = OnceLock::new();
|
||||
|
||||
/// Set the lang items.
|
||||
///
|
||||
/// This is a hack :(
|
||||
///
|
||||
/// Passing the lang items everywhere they are needed (especially text related
|
||||
/// things) is very painful. By storing them globally, in theory, we break
|
||||
/// incremental, but only when different sets of lang items are used in the same
|
||||
/// program. For this reason, if this function is called multiple times, the
|
||||
/// items must be the same (and this is enforced).
|
||||
pub fn set_lang_items(items: LangItems) {
|
||||
if let Err(items) = LANG_ITEMS.set(items) {
|
||||
let first = hash128(LANG_ITEMS.get().unwrap());
|
||||
let second = hash128(&items);
|
||||
assert_eq!(first, second, "set differing lang items");
|
||||
}
|
||||
}
|
||||
|
||||
/// Access a lang item.
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! __item {
|
||||
($name:ident) => {
|
||||
$crate::eval::LANG_ITEMS.get().unwrap().$name
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(inline)]
|
||||
pub use crate::__item as item;
|
272
crates/typst/src/eval/markup.rs
Normal file
272
crates/typst/src/eval/markup.rs
Normal file
@ -0,0 +1,272 @@
|
||||
use crate::diag::{warning, SourceResult};
|
||||
use crate::eval::{Eval, Vm};
|
||||
use crate::foundations::{Content, Label, NativeElement, Smart, Unlabellable, Value};
|
||||
use crate::math::EquationElem;
|
||||
use crate::model::{
|
||||
EmphElem, EnumItem, HeadingElem, LinkElem, ListItem, ParbreakElem, RefElem,
|
||||
StrongElem, Supplement, TermItem,
|
||||
};
|
||||
use crate::symbols::Symbol;
|
||||
use crate::syntax::ast::{self, AstNode};
|
||||
use crate::text::{LinebreakElem, RawElem, SmartQuoteElem, SpaceElem, TextElem};
|
||||
|
||||
impl Eval for ast::Markup<'_> {
|
||||
type Output = Content;
|
||||
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
eval_markup(vm, &mut self.exprs())
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate a stream of markup.
|
||||
fn eval_markup<'a>(
|
||||
vm: &mut Vm,
|
||||
exprs: &mut impl Iterator<Item = ast::Expr<'a>>,
|
||||
) -> SourceResult<Content> {
|
||||
let flow = vm.flow.take();
|
||||
let mut seq = Vec::with_capacity(exprs.size_hint().1.unwrap_or_default());
|
||||
|
||||
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, exprs)?.styled_with_map(styles))
|
||||
}
|
||||
ast::Expr::Show(show) => {
|
||||
let recipe = show.eval(vm)?;
|
||||
if vm.flow.is_some() {
|
||||
break;
|
||||
}
|
||||
|
||||
let tail = eval_markup(vm, exprs)?;
|
||||
seq.push(tail.styled_with_recipe(vm, recipe)?)
|
||||
}
|
||||
expr => match expr.eval(vm)? {
|
||||
Value::Label(label) => {
|
||||
if let Some(elem) =
|
||||
seq.iter_mut().rev().find(|node| !node.can::<dyn Unlabellable>())
|
||||
{
|
||||
*elem = std::mem::take(elem).labelled(label);
|
||||
}
|
||||
}
|
||||
value => seq.push(value.display().spanned(expr.span())),
|
||||
},
|
||||
}
|
||||
|
||||
if vm.flow.is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if flow.is_some() {
|
||||
vm.flow = flow;
|
||||
}
|
||||
|
||||
Ok(Content::sequence(seq))
|
||||
}
|
||||
|
||||
impl Eval for ast::Text<'_> {
|
||||
type Output = Content;
|
||||
|
||||
#[tracing::instrument(name = "Text::eval", skip_all)]
|
||||
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||
Ok(TextElem::packed(self.get().clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Space<'_> {
|
||||
type Output = Content;
|
||||
|
||||
#[tracing::instrument(name = "Space::eval", skip_all)]
|
||||
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||
Ok(SpaceElem::new().pack())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Linebreak<'_> {
|
||||
type Output = Content;
|
||||
|
||||
#[tracing::instrument(name = "Linebreak::eval", skip_all)]
|
||||
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||
Ok(LinebreakElem::new().pack())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Parbreak<'_> {
|
||||
type Output = Content;
|
||||
|
||||
#[tracing::instrument(name = "Parbreak::eval", skip_all)]
|
||||
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||
Ok(ParbreakElem::new().pack())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Escape<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "Escape::eval", skip_all)]
|
||||
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||
Ok(Value::Symbol(Symbol::single(self.get())))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Shorthand<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "Shorthand::eval", skip_all)]
|
||||
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||
Ok(Value::Symbol(Symbol::single(self.get())))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::SmartQuote<'_> {
|
||||
type Output = Content;
|
||||
|
||||
#[tracing::instrument(name = "SmartQuote::eval", skip_all)]
|
||||
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||
Ok(SmartQuoteElem::new().with_double(self.double()).pack())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Strong<'_> {
|
||||
type Output = Content;
|
||||
|
||||
#[tracing::instrument(name = "Strong::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let body = self.body();
|
||||
if body.exprs().next().is_none() {
|
||||
vm.vt
|
||||
.tracer
|
||||
.warn(warning!(self.span(), "no text within stars").with_hint(
|
||||
"using multiple consecutive stars (e.g. **) has no additional effect",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(StrongElem::new(body.eval(vm)?).pack())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Emph<'_> {
|
||||
type Output = Content;
|
||||
|
||||
#[tracing::instrument(name = "Emph::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let body = self.body();
|
||||
if body.exprs().next().is_none() {
|
||||
vm.vt
|
||||
.tracer
|
||||
.warn(warning!(self.span(), "no text within underscores").with_hint(
|
||||
"using multiple consecutive underscores (e.g. __) has no additional effect"
|
||||
));
|
||||
}
|
||||
|
||||
Ok(EmphElem::new(body.eval(vm)?).pack())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Raw<'_> {
|
||||
type Output = Content;
|
||||
|
||||
#[tracing::instrument(name = "Raw::eval", skip_all)]
|
||||
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let mut elem = RawElem::new(self.text()).with_block(self.block());
|
||||
if let Some(lang) = self.lang() {
|
||||
elem.push_lang(Some(lang.into()));
|
||||
}
|
||||
Ok(elem.pack())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Link<'_> {
|
||||
type Output = Content;
|
||||
|
||||
#[tracing::instrument(name = "Link::eval", skip_all)]
|
||||
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||
Ok(LinkElem::from_url(self.get().clone()).pack())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Label<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "Label::eval", skip_all)]
|
||||
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||
Ok(Value::Label(Label::new(self.get())))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Ref<'_> {
|
||||
type Output = Content;
|
||||
|
||||
#[tracing::instrument(name = "Ref::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let target = Label::new(self.target());
|
||||
let mut elem = RefElem::new(target);
|
||||
if let Some(supplement) = self.supplement() {
|
||||
elem.push_supplement(Smart::Custom(Some(Supplement::Content(
|
||||
supplement.eval(vm)?,
|
||||
))));
|
||||
}
|
||||
Ok(elem.pack())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Heading<'_> {
|
||||
type Output = Content;
|
||||
|
||||
#[tracing::instrument(name = "Heading::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let level = self.level();
|
||||
let body = self.body().eval(vm)?;
|
||||
Ok(HeadingElem::new(body).with_level(level).pack())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::ListItem<'_> {
|
||||
type Output = Content;
|
||||
|
||||
#[tracing::instrument(name = "ListItem::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
Ok(ListItem::new(self.body().eval(vm)?).pack())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::EnumItem<'_> {
|
||||
type Output = Content;
|
||||
|
||||
#[tracing::instrument(name = "EnumItem::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let body = self.body().eval(vm)?;
|
||||
let mut elem = EnumItem::new(body);
|
||||
if let Some(number) = self.number() {
|
||||
elem.push_number(Some(number));
|
||||
}
|
||||
Ok(elem.pack())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::TermItem<'_> {
|
||||
type Output = Content;
|
||||
|
||||
#[tracing::instrument(name = "TermItem::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let term = self.term().eval(vm)?;
|
||||
let description = self.description().eval(vm)?;
|
||||
Ok(TermItem::new(term, description).pack())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Equation<'_> {
|
||||
type Output = Content;
|
||||
|
||||
#[tracing::instrument(name = "Equation::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let body = self.body().eval(vm)?;
|
||||
let block = self.block();
|
||||
Ok(EquationElem::new(body).with_block(block).pack())
|
||||
}
|
||||
}
|
113
crates/typst/src/eval/math.rs
Normal file
113
crates/typst/src/eval/math.rs
Normal file
@ -0,0 +1,113 @@
|
||||
use ecow::eco_format;
|
||||
|
||||
use crate::diag::{At, SourceResult};
|
||||
use crate::eval::{Eval, Vm};
|
||||
use crate::foundations::{Content, NativeElement, Value};
|
||||
use crate::math::{AlignPointElem, AttachElem, FracElem, LrElem, PrimesElem, RootElem};
|
||||
use crate::syntax::ast::{self, AstNode};
|
||||
use crate::text::TextElem;
|
||||
|
||||
impl Eval for ast::Math<'_> {
|
||||
type Output = Content;
|
||||
|
||||
#[tracing::instrument(name = "Math::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
Ok(Content::sequence(
|
||||
self.exprs()
|
||||
.map(|expr| expr.eval_display(vm))
|
||||
.collect::<SourceResult<Vec<_>>>()?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::MathIdent<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "MathIdent::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
vm.scopes.get_in_math(&self).cloned().at(self.span())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::MathAlignPoint<'_> {
|
||||
type Output = Content;
|
||||
|
||||
#[tracing::instrument(name = "MathAlignPoint::eval", skip_all)]
|
||||
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||
Ok(AlignPointElem::new().pack())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::MathDelimited<'_> {
|
||||
type Output = Content;
|
||||
|
||||
#[tracing::instrument(name = "MathDelimited::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let open = self.open().eval_display(vm)?;
|
||||
let body = self.body().eval(vm)?;
|
||||
let close = self.close().eval_display(vm)?;
|
||||
Ok(LrElem::new(open + body + close).pack())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::MathAttach<'_> {
|
||||
type Output = Content;
|
||||
|
||||
#[tracing::instrument(name = "MathAttach::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let base = self.base().eval_display(vm)?;
|
||||
let mut elem = AttachElem::new(base);
|
||||
|
||||
if let Some(expr) = self.top() {
|
||||
elem.push_t(Some(expr.eval_display(vm)?));
|
||||
} else if let Some(primes) = self.primes() {
|
||||
elem.push_t(Some(primes.eval(vm)?));
|
||||
}
|
||||
|
||||
if let Some(expr) = self.bottom() {
|
||||
elem.push_b(Some(expr.eval_display(vm)?));
|
||||
}
|
||||
|
||||
Ok(elem.pack())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::MathPrimes<'_> {
|
||||
type Output = Content;
|
||||
|
||||
#[tracing::instrument(name = "MathPrimes::eval", skip_all)]
|
||||
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||
Ok(PrimesElem::new(self.count()).pack())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::MathFrac<'_> {
|
||||
type Output = Content;
|
||||
|
||||
#[tracing::instrument(name = "MathFrac::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let num = self.num().eval_display(vm)?;
|
||||
let denom = self.denom().eval_display(vm)?;
|
||||
Ok(FracElem::new(num, denom).pack())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::MathRoot<'_> {
|
||||
type Output = Content;
|
||||
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let index = self.index().map(|i| TextElem::packed(eco_format!("{i}")));
|
||||
let radicand = self.radicand().eval_display(vm)?;
|
||||
Ok(RootElem::new(radicand).with_index(index).pack())
|
||||
}
|
||||
}
|
||||
|
||||
trait ExprExt {
|
||||
fn eval_display(&self, vm: &mut Vm) -> SourceResult<Content>;
|
||||
}
|
||||
|
||||
impl ExprExt for ast::Expr<'_> {
|
||||
fn eval_display(&self, vm: &mut Vm) -> SourceResult<Content> {
|
||||
Ok(self.eval(vm)?.display().spanned(self.span()))
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -4,10 +4,102 @@ use std::cmp::Ordering;
|
||||
|
||||
use ecow::eco_format;
|
||||
|
||||
use crate::diag::{bail, StrResult};
|
||||
use crate::eval::{format_str, item, IntoValue, Regex, Repr, Smart, Value};
|
||||
use crate::geom::{Align, Length, Numeric, Rel, Stroke};
|
||||
use Value::*;
|
||||
use crate::diag::{bail, At, SourceResult, StrResult};
|
||||
use crate::eval::{access_dict, Access, Eval, Vm};
|
||||
use crate::foundations::{format_str, Datetime, IntoValue, Regex, Repr, Smart, Value};
|
||||
use crate::layout::{Align, Length, Rel};
|
||||
use crate::syntax::ast::{self, AstNode};
|
||||
use crate::text::TextElem;
|
||||
use crate::util::Numeric;
|
||||
use crate::visualize::Stroke;
|
||||
|
||||
impl Eval for ast::Unary<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "Unary::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let value = self.expr().eval(vm)?;
|
||||
let result = match self.op() {
|
||||
ast::UnOp::Pos => pos(value),
|
||||
ast::UnOp::Neg => neg(value),
|
||||
ast::UnOp::Not => not(value),
|
||||
};
|
||||
result.at(self.span())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::Binary<'_> {
|
||||
type Output = Value;
|
||||
|
||||
#[tracing::instrument(name = "Binary::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
match self.op() {
|
||||
ast::BinOp::Add => apply_binary(self, vm, add),
|
||||
ast::BinOp::Sub => apply_binary(self, vm, sub),
|
||||
ast::BinOp::Mul => apply_binary(self, vm, mul),
|
||||
ast::BinOp::Div => apply_binary(self, vm, div),
|
||||
ast::BinOp::And => apply_binary(self, vm, and),
|
||||
ast::BinOp::Or => apply_binary(self, vm, or),
|
||||
ast::BinOp::Eq => apply_binary(self, vm, eq),
|
||||
ast::BinOp::Neq => apply_binary(self, vm, neq),
|
||||
ast::BinOp::Lt => apply_binary(self, vm, lt),
|
||||
ast::BinOp::Leq => apply_binary(self, vm, leq),
|
||||
ast::BinOp::Gt => apply_binary(self, vm, gt),
|
||||
ast::BinOp::Geq => apply_binary(self, vm, geq),
|
||||
ast::BinOp::In => apply_binary(self, vm, in_),
|
||||
ast::BinOp::NotIn => apply_binary(self, vm, not_in),
|
||||
ast::BinOp::Assign => apply_assignment(self, vm, |_, b| Ok(b)),
|
||||
ast::BinOp::AddAssign => apply_assignment(self, vm, add),
|
||||
ast::BinOp::SubAssign => apply_assignment(self, vm, sub),
|
||||
ast::BinOp::MulAssign => apply_assignment(self, vm, mul),
|
||||
ast::BinOp::DivAssign => apply_assignment(self, vm, div),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply a basic binary operation.
|
||||
fn apply_binary(
|
||||
binary: ast::Binary,
|
||||
vm: &mut Vm,
|
||||
op: fn(Value, Value) -> StrResult<Value>,
|
||||
) -> SourceResult<Value> {
|
||||
let lhs = binary.lhs().eval(vm)?;
|
||||
|
||||
// Short-circuit boolean operations.
|
||||
if (binary.op() == ast::BinOp::And && lhs == false.into_value())
|
||||
|| (binary.op() == ast::BinOp::Or && lhs == true.into_value())
|
||||
{
|
||||
return Ok(lhs);
|
||||
}
|
||||
|
||||
let rhs = binary.rhs().eval(vm)?;
|
||||
op(lhs, rhs).at(binary.span())
|
||||
}
|
||||
|
||||
/// Apply an assignment operation.
|
||||
fn apply_assignment(
|
||||
binary: ast::Binary,
|
||||
vm: &mut Vm,
|
||||
op: fn(Value, Value) -> StrResult<Value>,
|
||||
) -> SourceResult<Value> {
|
||||
let rhs = binary.rhs().eval(vm)?;
|
||||
let lhs = binary.lhs();
|
||||
|
||||
// An assignment to a dictionary field is different from a normal access
|
||||
// since it can create the field instead of just modifying it.
|
||||
if binary.op() == ast::BinOp::Assign {
|
||||
if let ast::Expr::FieldAccess(access) = lhs {
|
||||
let dict = access_dict(vm, access)?;
|
||||
dict.insert(access.field().get().clone().into(), rhs);
|
||||
return Ok(Value::None);
|
||||
}
|
||||
}
|
||||
|
||||
let location = binary.lhs().access(vm)?;
|
||||
let lhs = std::mem::take(&mut *location);
|
||||
*location = op(lhs, rhs).at(binary.span())?;
|
||||
Ok(Value::None)
|
||||
}
|
||||
|
||||
/// Bail with a type mismatch error.
|
||||
macro_rules! mismatch {
|
||||
@ -18,6 +110,7 @@ macro_rules! mismatch {
|
||||
|
||||
/// Join a value with another value.
|
||||
pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
use Value::*;
|
||||
Ok(match (lhs, rhs) {
|
||||
(a, None) => a,
|
||||
(None, b) => b,
|
||||
@ -27,10 +120,10 @@ pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
(Symbol(a), Str(b)) => Str(format_str!("{a}{b}")),
|
||||
(Bytes(a), Bytes(b)) => Bytes(a + b),
|
||||
(Content(a), Content(b)) => Content(a + b),
|
||||
(Content(a), Symbol(b)) => Content(a + item!(text)(b.get().into())),
|
||||
(Content(a), Str(b)) => Content(a + item!(text)(b.into())),
|
||||
(Str(a), Content(b)) => Content(item!(text)(a.into()) + b),
|
||||
(Symbol(a), Content(b)) => Content(item!(text)(a.get().into()) + b),
|
||||
(Content(a), Symbol(b)) => Content(a + TextElem::packed(b.get())),
|
||||
(Content(a), Str(b)) => Content(a + TextElem::packed(b)),
|
||||
(Str(a), Content(b)) => Content(TextElem::packed(a) + b),
|
||||
(Symbol(a), Content(b)) => Content(TextElem::packed(a.get()) + b),
|
||||
(Array(a), Array(b)) => Array(a + b),
|
||||
(Dict(a), Dict(b)) => Dict(a + b),
|
||||
|
||||
@ -44,6 +137,7 @@ pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
|
||||
/// Apply the unary plus operator to a value.
|
||||
pub fn pos(value: Value) -> StrResult<Value> {
|
||||
use Value::*;
|
||||
Ok(match value {
|
||||
Int(v) => Int(v),
|
||||
Float(v) => Float(v),
|
||||
@ -68,6 +162,7 @@ pub fn pos(value: Value) -> StrResult<Value> {
|
||||
|
||||
/// Compute the negation of a value.
|
||||
pub fn neg(value: Value) -> StrResult<Value> {
|
||||
use Value::*;
|
||||
Ok(match value {
|
||||
Int(v) => Int(v.checked_neg().ok_or_else(too_large)?),
|
||||
Float(v) => Float(-v),
|
||||
@ -84,6 +179,7 @@ pub fn neg(value: Value) -> StrResult<Value> {
|
||||
|
||||
/// Compute the sum of two values.
|
||||
pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
use Value::*;
|
||||
Ok(match (lhs, rhs) {
|
||||
(a, None) => a,
|
||||
(None, b) => b,
|
||||
@ -115,10 +211,10 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
(Symbol(a), Str(b)) => Str(format_str!("{a}{b}")),
|
||||
(Bytes(a), Bytes(b)) => Bytes(a + b),
|
||||
(Content(a), Content(b)) => Content(a + b),
|
||||
(Content(a), Symbol(b)) => Content(a + item!(text)(b.get().into())),
|
||||
(Content(a), Str(b)) => Content(a + item!(text)(b.into())),
|
||||
(Str(a), Content(b)) => Content(item!(text)(a.into()) + b),
|
||||
(Symbol(a), Content(b)) => Content(item!(text)(a.get().into()) + b),
|
||||
(Content(a), Symbol(b)) => Content(a + TextElem::packed(b.get())),
|
||||
(Content(a), Str(b)) => Content(a + TextElem::packed(b)),
|
||||
(Str(a), Content(b)) => Content(TextElem::packed(a) + b),
|
||||
(Symbol(a), Content(b)) => Content(TextElem::packed(a.get()) + b),
|
||||
|
||||
(Array(a), Array(b)) => Array(a + b),
|
||||
(Dict(a), Dict(b)) => Dict(a + b),
|
||||
@ -161,6 +257,7 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
|
||||
/// Compute the difference of two values.
|
||||
pub fn sub(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
use Value::*;
|
||||
Ok(match (lhs, rhs) {
|
||||
(Int(a), Int(b)) => Int(a.checked_sub(b).ok_or_else(too_large)?),
|
||||
(Int(a), Float(b)) => Float(a as f64 - b),
|
||||
@ -193,6 +290,7 @@ pub fn sub(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
|
||||
/// Compute the product of two values.
|
||||
pub fn mul(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
use Value::*;
|
||||
Ok(match (lhs, rhs) {
|
||||
(Int(a), Int(b)) => Int(a.checked_mul(b).ok_or_else(too_large)?),
|
||||
(Int(a), Float(b)) => Float(a as f64 * b),
|
||||
@ -251,6 +349,7 @@ pub fn mul(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
|
||||
/// Compute the quotient of two values.
|
||||
pub fn div(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
use Value::*;
|
||||
if is_zero(&rhs) {
|
||||
bail!("cannot divide by zero");
|
||||
}
|
||||
@ -295,6 +394,7 @@ pub fn div(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
|
||||
/// Whether a value is a numeric zero.
|
||||
fn is_zero(v: &Value) -> bool {
|
||||
use Value::*;
|
||||
match *v {
|
||||
Int(v) => v == 0,
|
||||
Float(v) => v == 0.0,
|
||||
@ -322,7 +422,7 @@ fn try_div_relative(a: Rel<Length>, b: Rel<Length>) -> StrResult<f64> {
|
||||
/// Compute the logical "not" of a value.
|
||||
pub fn not(value: Value) -> StrResult<Value> {
|
||||
match value {
|
||||
Bool(b) => Ok(Bool(!b)),
|
||||
Value::Bool(b) => Ok(Value::Bool(!b)),
|
||||
v => mismatch!("cannot apply 'not' to {}", v),
|
||||
}
|
||||
}
|
||||
@ -330,7 +430,7 @@ pub fn not(value: Value) -> StrResult<Value> {
|
||||
/// Compute the logical "and" of two values.
|
||||
pub fn and(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
match (lhs, rhs) {
|
||||
(Bool(a), Bool(b)) => Ok(Bool(a && b)),
|
||||
(Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(a && b)),
|
||||
(a, b) => mismatch!("cannot apply 'and' to {} and {}", a, b),
|
||||
}
|
||||
}
|
||||
@ -338,19 +438,19 @@ pub fn and(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
/// Compute the logical "or" of two values.
|
||||
pub fn or(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
match (lhs, rhs) {
|
||||
(Bool(a), Bool(b)) => Ok(Bool(a || b)),
|
||||
(Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(a || b)),
|
||||
(a, b) => mismatch!("cannot apply 'or' to {} and {}", a, b),
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute whether two values are equal.
|
||||
pub fn eq(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
Ok(Bool(equal(&lhs, &rhs)))
|
||||
Ok(Value::Bool(equal(&lhs, &rhs)))
|
||||
}
|
||||
|
||||
/// Compute whether two values are unequal.
|
||||
pub fn neq(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
Ok(Bool(!equal(&lhs, &rhs)))
|
||||
Ok(Value::Bool(!equal(&lhs, &rhs)))
|
||||
}
|
||||
|
||||
macro_rules! comparison {
|
||||
@ -358,7 +458,7 @@ macro_rules! comparison {
|
||||
/// Compute how a value compares with another value.
|
||||
pub fn $name(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
let ordering = compare(&lhs, &rhs)?;
|
||||
Ok(Bool(matches!(ordering, $($pat)*)))
|
||||
Ok(Value::Bool(matches!(ordering, $($pat)*)))
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -370,6 +470,7 @@ comparison!(geq, ">=", Ordering::Greater | Ordering::Equal);
|
||||
|
||||
/// Determine whether two values are equal.
|
||||
pub fn equal(lhs: &Value, rhs: &Value) -> bool {
|
||||
use Value::*;
|
||||
match (lhs, rhs) {
|
||||
// Compare reflexively.
|
||||
(None, None) => true,
|
||||
@ -418,6 +519,7 @@ pub fn equal(lhs: &Value, rhs: &Value) -> bool {
|
||||
|
||||
/// Compare two values.
|
||||
pub fn compare(lhs: &Value, rhs: &Value) -> StrResult<Ordering> {
|
||||
use Value::*;
|
||||
Ok(match (lhs, rhs) {
|
||||
(Bool(a), Bool(b)) => a.cmp(b),
|
||||
(Int(a), Int(b)) => a.cmp(b),
|
||||
@ -452,7 +554,7 @@ fn try_cmp_values<T: PartialOrd + Repr>(a: &T, b: &T) -> StrResult<Ordering> {
|
||||
}
|
||||
|
||||
/// Try to compare two datetimes.
|
||||
fn try_cmp_datetimes(a: &super::Datetime, b: &super::Datetime) -> StrResult<Ordering> {
|
||||
fn try_cmp_datetimes(a: &Datetime, b: &Datetime) -> StrResult<Ordering> {
|
||||
a.partial_cmp(b)
|
||||
.ok_or_else(|| eco_format!("cannot compare {} and {}", a.kind(), b.kind()))
|
||||
}
|
||||
@ -460,7 +562,7 @@ fn try_cmp_datetimes(a: &super::Datetime, b: &super::Datetime) -> StrResult<Orde
|
||||
/// Test whether one value is "in" another one.
|
||||
pub fn in_(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
if let Some(b) = contains(&lhs, &rhs) {
|
||||
Ok(Bool(b))
|
||||
Ok(Value::Bool(b))
|
||||
} else {
|
||||
mismatch!("cannot apply 'in' to {} and {}", lhs, rhs)
|
||||
}
|
||||
@ -469,7 +571,7 @@ pub fn in_(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
/// Test whether one value is "not in" another one.
|
||||
pub fn not_in(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
if let Some(b) = contains(&lhs, &rhs) {
|
||||
Ok(Bool(!b))
|
||||
Ok(Value::Bool(!b))
|
||||
} else {
|
||||
mismatch!("cannot apply 'not in' to {} and {}", lhs, rhs)
|
||||
}
|
||||
@ -477,6 +579,7 @@ pub fn not_in(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
|
||||
/// Test for containment.
|
||||
pub fn contains(lhs: &Value, rhs: &Value) -> Option<bool> {
|
||||
use Value::*;
|
||||
match (lhs, rhs) {
|
||||
(Str(a), Str(b)) => Some(b.as_str().contains(a.as_str())),
|
||||
(Dyn(a), Str(b)) => a.downcast::<Regex>().map(|regex| regex.is_match(b)),
|
||||
|
51
crates/typst/src/eval/rules.rs
Normal file
51
crates/typst/src/eval/rules.rs
Normal file
@ -0,0 +1,51 @@
|
||||
use crate::diag::{At, SourceResult};
|
||||
use crate::eval::{Eval, Vm};
|
||||
use crate::foundations::{Func, Recipe, ShowableSelector, Styles, Transformation};
|
||||
use crate::syntax::ast::{self, AstNode};
|
||||
|
||||
impl Eval for ast::SetRule<'_> {
|
||||
type Output = Styles;
|
||||
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
if let Some(condition) = self.condition() {
|
||||
if !condition.eval(vm)?.cast::<bool>().at(condition.span())? {
|
||||
return Ok(Styles::new());
|
||||
}
|
||||
}
|
||||
|
||||
let target = self.target();
|
||||
let target = target
|
||||
.eval(vm)?
|
||||
.cast::<Func>()
|
||||
.and_then(|func| {
|
||||
func.element().ok_or_else(|| {
|
||||
"only element functions can be used in set rules".into()
|
||||
})
|
||||
})
|
||||
.at(target.span())?;
|
||||
let args = self.args().eval(vm)?;
|
||||
Ok(target.set(vm, args)?.spanned(self.span()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::ShowRule<'_> {
|
||||
type Output = Recipe;
|
||||
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let selector = self
|
||||
.selector()
|
||||
.map(|sel| sel.eval(vm)?.cast::<ShowableSelector>().at(sel.span()))
|
||||
.transpose()?
|
||||
.map(|selector| selector.0);
|
||||
|
||||
let transform = self.transform();
|
||||
let span = transform.span();
|
||||
|
||||
let transform = match transform {
|
||||
ast::Expr::Set(set) => Transformation::Style(set.eval(vm)?),
|
||||
expr => expr.eval(vm)?.cast::<Transformation>().at(span)?,
|
||||
};
|
||||
|
||||
Ok(Recipe { span, selector, transform })
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ use std::collections::HashSet;
|
||||
use ecow::EcoVec;
|
||||
|
||||
use crate::diag::SourceDiagnostic;
|
||||
use crate::eval::Value;
|
||||
use crate::foundations::Value;
|
||||
use crate::syntax::{FileId, Span};
|
||||
use crate::util::hash128;
|
||||
|
||||
@ -44,7 +44,7 @@ impl Tracer {
|
||||
|
||||
#[comemo::track]
|
||||
impl Tracer {
|
||||
/// The inspeted span if it is part of the given source file.
|
||||
/// The inspected span if it is part of the given source file.
|
||||
pub fn inspected(&self, id: FileId) -> Option<Span> {
|
||||
if self.inspected.and_then(Span::id) == Some(id) {
|
||||
self.inspected
|
||||
|
127
crates/typst/src/eval/vm.rs
Normal file
127
crates/typst/src/eval/vm.rs
Normal file
@ -0,0 +1,127 @@
|
||||
use comemo::{Track, Tracked, Validate};
|
||||
|
||||
use crate::diag::{bail, StrResult};
|
||||
use crate::eval::FlowEvent;
|
||||
use crate::foundations::{IntoValue, Scopes};
|
||||
use crate::layout::Vt;
|
||||
use crate::syntax::ast::{self, AstNode};
|
||||
use crate::syntax::{FileId, Span};
|
||||
use crate::World;
|
||||
|
||||
/// A virtual machine.
|
||||
///
|
||||
/// Holds the state needed to [evaluate](crate::eval::eval()) Typst sources. A new
|
||||
/// virtual machine is created for each module evaluation and function call.
|
||||
pub struct Vm<'a> {
|
||||
/// The underlying virtual typesetter.
|
||||
pub(crate) vt: Vt<'a>,
|
||||
/// The route of source ids the VM took to reach its current location.
|
||||
pub(crate) route: Tracked<'a, Route<'a>>,
|
||||
/// The id of the currently evaluated file.
|
||||
pub(crate) file: Option<FileId>,
|
||||
/// A control flow event that is currently happening.
|
||||
pub(crate) flow: Option<FlowEvent>,
|
||||
/// The stack of scopes.
|
||||
pub(crate) scopes: Scopes<'a>,
|
||||
/// The current call depth.
|
||||
pub(crate) depth: usize,
|
||||
/// A span that is currently under inspection.
|
||||
pub(crate) inspected: Option<Span>,
|
||||
}
|
||||
|
||||
impl<'a> Vm<'a> {
|
||||
/// Create a new virtual machine.
|
||||
pub fn new(
|
||||
vt: Vt<'a>,
|
||||
route: Tracked<'a, Route>,
|
||||
file: Option<FileId>,
|
||||
scopes: Scopes<'a>,
|
||||
) -> Self {
|
||||
let inspected = file.and_then(|id| vt.tracer.inspected(id));
|
||||
Self {
|
||||
vt,
|
||||
route,
|
||||
file,
|
||||
flow: None,
|
||||
scopes,
|
||||
depth: 0,
|
||||
inspected,
|
||||
}
|
||||
}
|
||||
|
||||
/// Access the underlying world.
|
||||
pub fn world(&self) -> Tracked<'a, dyn World + 'a> {
|
||||
self.vt.world
|
||||
}
|
||||
|
||||
/// The id of the currently evaluated file.
|
||||
///
|
||||
/// Returns `None` if the VM is in a detached context, e.g. when evaluating
|
||||
/// a user-provided string.
|
||||
pub fn file(&self) -> Option<FileId> {
|
||||
self.file
|
||||
}
|
||||
|
||||
/// Resolve a path relative to the currently evaluated file.
|
||||
pub fn resolve_path(&self, path: &str) -> StrResult<FileId> {
|
||||
let Some(file) = self.file else {
|
||||
bail!("cannot access file system from here");
|
||||
};
|
||||
|
||||
Ok(file.join(path))
|
||||
}
|
||||
|
||||
/// Define a variable in the current scope.
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn define(&mut self, var: ast::Ident, value: impl IntoValue) {
|
||||
let value = value.into_value();
|
||||
if self.inspected == Some(var.span()) {
|
||||
self.vt.tracer.value(value.clone());
|
||||
}
|
||||
self.scopes.top.define(var.get().clone(), value);
|
||||
}
|
||||
}
|
||||
|
||||
/// A route of source ids.
|
||||
#[derive(Default)]
|
||||
pub struct Route<'a> {
|
||||
// We need to override the constraint's lifetime here so that `Tracked` is
|
||||
// covariant over the constraint. If it becomes invariant, we're in for a
|
||||
// world of lifetime pain.
|
||||
outer: Option<Tracked<'a, Self, <Route<'static> as Validate>::Constraint>>,
|
||||
id: Option<FileId>,
|
||||
}
|
||||
|
||||
impl<'a> Route<'a> {
|
||||
/// Create a new route with just one entry.
|
||||
pub fn new(id: Option<FileId>) -> Self {
|
||||
Self { id, outer: None }
|
||||
}
|
||||
|
||||
/// Insert a new id into the route.
|
||||
///
|
||||
/// You must guarantee that `outer` lives longer than the resulting
|
||||
/// route is ever used.
|
||||
pub fn insert(outer: Tracked<'a, Self>, id: FileId) -> Self {
|
||||
Route { outer: Some(outer), id: Some(id) }
|
||||
}
|
||||
|
||||
/// Start tracking this locator.
|
||||
///
|
||||
/// In comparison to [`Track::track`], this method skips this chain link
|
||||
/// if it does not contribute anything.
|
||||
pub fn track(&self) -> Tracked<'_, Self> {
|
||||
match self.outer {
|
||||
Some(outer) if self.id.is_none() => outer,
|
||||
_ => Track::track(self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[comemo::track]
|
||||
impl<'a> Route<'a> {
|
||||
/// Whether the given id is part of the route.
|
||||
pub fn contains(&self, id: FileId) -> bool {
|
||||
self.id == Some(id) || self.outer.map_or(false, |outer| outer.contains(id))
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ use std::fmt::{self, Debug, Formatter};
|
||||
use ecow::{eco_format, eco_vec, EcoString, EcoVec};
|
||||
|
||||
use crate::diag::{bail, At, SourceDiagnostic, SourceResult};
|
||||
use crate::eval::{
|
||||
use crate::foundations::{
|
||||
func, repr, scope, ty, Array, Dict, FromValue, IntoValue, Repr, Str, Value,
|
||||
};
|
||||
use crate::syntax::{Span, Spanned};
|
@ -8,9 +8,10 @@ use serde::{Deserialize, Serialize};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::diag::{At, SourceResult, StrResult};
|
||||
use crate::eval::{
|
||||
cast, func, ops, repr, scope, ty, Args, Bytes, CastInfo, FromValue, Func, IntoValue,
|
||||
Reflect, Repr, Value, Version, Vm,
|
||||
use crate::eval::{ops, Vm};
|
||||
use crate::foundations::{
|
||||
cast, func, repr, scope, ty, Args, Bytes, CastInfo, FromValue, Func, IntoValue,
|
||||
Reflect, Repr, Value, Version,
|
||||
};
|
||||
use crate::syntax::Span;
|
||||
|
||||
@ -19,15 +20,15 @@ use crate::syntax::Span;
|
||||
#[doc(hidden)]
|
||||
macro_rules! __array {
|
||||
($value:expr; $count:expr) => {
|
||||
$crate::eval::Array::from($crate::eval::eco_vec![
|
||||
$crate::eval::IntoValue::into_value($value);
|
||||
$crate::foundations::Array::from($crate::foundations::eco_vec![
|
||||
$crate::foundations::IntoValue::into_value($value);
|
||||
$count
|
||||
])
|
||||
};
|
||||
|
||||
($($value:expr),* $(,)?) => {
|
||||
$crate::eval::Array::from($crate::eval::eco_vec![$(
|
||||
$crate::eval::IntoValue::into_value($value)
|
||||
$crate::foundations::Array::from($crate::foundations::eco_vec![$(
|
||||
$crate::foundations::IntoValue::into_value($value)
|
||||
),*])
|
||||
};
|
||||
}
|
||||
@ -35,9 +36,6 @@ macro_rules! __array {
|
||||
#[doc(inline)]
|
||||
pub use crate::__array as array;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use ecow::eco_vec;
|
||||
|
||||
/// A sequence of values.
|
||||
///
|
||||
/// You can construct an array by enclosing a comma-separated sequence of values
|
||||
@ -736,7 +734,7 @@ impl Array {
|
||||
vec.make_mut().sort_by(|a, b| {
|
||||
// Until we get `try` blocks :)
|
||||
match (key_of(a.clone()), key_of(b.clone())) {
|
||||
(Ok(a), Ok(b)) => super::ops::compare(&a, &b).unwrap_or_else(|err| {
|
||||
(Ok(a), Ok(b)) => ops::compare(&a, &b).unwrap_or_else(|err| {
|
||||
if result.is_ok() {
|
||||
result = Err(err).at(span);
|
||||
}
|
||||
@ -790,7 +788,7 @@ impl Array {
|
||||
}
|
||||
|
||||
for second in out.iter() {
|
||||
if super::ops::equal(&key, &key_of(second.clone())?) {
|
||||
if ops::equal(&key, &key_of(second.clone())?) {
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
@ -2,8 +2,10 @@ use ecow::EcoString;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
|
||||
use crate::diag::StrResult;
|
||||
use crate::eval::{ty, CastInfo, FromValue, IntoValue, Reflect, Repr, Type, Value};
|
||||
use crate::model::{Fold, Resolve, StyleChain};
|
||||
use crate::foundations::{
|
||||
ty, CastInfo, Fold, FromValue, IntoValue, Reflect, Repr, Resolve, StyleChain, Type,
|
||||
Value,
|
||||
};
|
||||
|
||||
/// A value that indicates a smart default.
|
||||
///
|
@ -1,6 +1,6 @@
|
||||
use ecow::EcoString;
|
||||
|
||||
use super::{ty, Repr};
|
||||
use crate::foundations::{ty, Repr};
|
||||
|
||||
/// A type with two states.
|
||||
///
|
@ -8,7 +8,7 @@ use ecow::{eco_format, EcoString};
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
use crate::diag::{bail, StrResult};
|
||||
use crate::eval::{cast, func, scope, ty, Array, Reflect, Repr, Str, Value};
|
||||
use crate::foundations::{cast, func, scope, ty, Array, Reflect, Repr, Str, Value};
|
||||
|
||||
/// A sequence of bytes.
|
||||
///
|
@ -4,20 +4,15 @@ use std::cmp;
|
||||
use std::cmp::Ordering;
|
||||
use std::ops::{Div, Rem};
|
||||
|
||||
use typst::eval::{Module, Scope};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Hook up all calculation definitions.
|
||||
pub(super) fn define(global: &mut Scope) {
|
||||
global.category("calculate");
|
||||
global.define_module(module());
|
||||
}
|
||||
use crate::diag::{bail, At, SourceResult, StrResult};
|
||||
use crate::eval::ops;
|
||||
use crate::foundations::{cast, func, IntoValue, Module, Scope, Value};
|
||||
use crate::layout::{Angle, Fr, Length, Ratio};
|
||||
use crate::syntax::{Span, Spanned};
|
||||
|
||||
/// A module with calculation definitions.
|
||||
fn module() -> Module {
|
||||
pub fn module() -> Module {
|
||||
let mut scope = Scope::new();
|
||||
scope.category("calculate");
|
||||
scope.define_func::<abs>();
|
||||
scope.define_func::<pow>();
|
||||
scope.define_func::<exp>();
|
||||
@ -747,7 +742,7 @@ fn minmax(
|
||||
};
|
||||
|
||||
for Spanned { v, span } in iter {
|
||||
let ordering = typst::eval::ops::compare(&v, &extremum).at(span)?;
|
||||
let ordering = ops::compare(&v, &extremum).at(span)?;
|
||||
if ordering == goal {
|
||||
extremum = v;
|
||||
}
|
@ -1,5 +1,3 @@
|
||||
pub use typst_macros::{cast, Cast};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Write;
|
||||
use std::hash::Hash;
|
||||
@ -11,9 +9,12 @@ use smallvec::SmallVec;
|
||||
use unicode_math_class::MathClass;
|
||||
|
||||
use crate::diag::{At, SourceResult, StrResult};
|
||||
use crate::eval::{repr, Repr, Type, Value};
|
||||
use crate::foundations::{repr, Repr, Type, Value};
|
||||
use crate::syntax::{Span, Spanned};
|
||||
|
||||
#[doc(inline)]
|
||||
pub use typst_macros::{cast, Cast};
|
||||
|
||||
/// Determine details of a type.
|
||||
///
|
||||
/// Type casting works as follows:
|
@ -7,19 +7,20 @@ use std::sync::Arc;
|
||||
use comemo::Prehashed;
|
||||
use ecow::{eco_format, EcoString};
|
||||
use serde::{Serialize, Serializer};
|
||||
use smallvec::SmallVec;
|
||||
use typst_macros::elem;
|
||||
use smallvec::smallvec;
|
||||
|
||||
use crate::diag::{SourceResult, StrResult};
|
||||
use crate::doc::Meta;
|
||||
use crate::eval::{
|
||||
func, repr, scope, ty, Dict, FromValue, IntoValue, Repr, Str, Value, Vm,
|
||||
};
|
||||
use crate::model::{
|
||||
Behave, Behaviour, Element, Guard, Label, Location, NativeElement, Recipe, Selector,
|
||||
Style, Styles,
|
||||
use crate::eval::Vm;
|
||||
use crate::foundations::{
|
||||
elem, func, scope, ty, Dict, Element, FromValue, Guard, IntoValue, Label,
|
||||
NativeElement, Recipe, Repr, Selector, Str, Style, Styles, Value,
|
||||
};
|
||||
use crate::introspection::{Location, Meta, MetaElem};
|
||||
use crate::layout::{Align, AlignElem, Axes, Length, MoveElem, PadElem, Rel, Sides};
|
||||
use crate::model::{Destination, EmphElem, StrongElem};
|
||||
use crate::syntax::Span;
|
||||
use crate::text::UnderlineElem;
|
||||
use crate::util::fat;
|
||||
|
||||
/// A piece of document content.
|
||||
///
|
||||
@ -457,6 +458,57 @@ impl Content {
|
||||
}
|
||||
}
|
||||
|
||||
impl Content {
|
||||
/// Strongly emphasize this content.
|
||||
pub fn strong(self) -> Self {
|
||||
StrongElem::new(self).pack()
|
||||
}
|
||||
|
||||
/// Emphasize this content.
|
||||
pub fn emph(self) -> Self {
|
||||
EmphElem::new(self).pack()
|
||||
}
|
||||
|
||||
/// Underline this content.
|
||||
pub fn underlined(self) -> Self {
|
||||
UnderlineElem::new(self).pack()
|
||||
}
|
||||
|
||||
/// Link the content somewhere.
|
||||
pub fn linked(self, dest: Destination) -> Self {
|
||||
self.styled(MetaElem::set_data(smallvec![Meta::Link(dest)]))
|
||||
}
|
||||
|
||||
/// Make the content linkable by `.linked(Destination::Location(loc))`.
|
||||
///
|
||||
/// Should be used in combination with [`Location::variant`].
|
||||
pub fn backlinked(self, loc: Location) -> Self {
|
||||
let mut backlink = Content::empty();
|
||||
backlink.set_location(loc);
|
||||
self.styled(MetaElem::set_data(smallvec![Meta::Elem(backlink)]))
|
||||
}
|
||||
|
||||
/// Set alignments for this content.
|
||||
pub fn aligned(self, align: Align) -> Self {
|
||||
self.styled(AlignElem::set_alignment(align))
|
||||
}
|
||||
|
||||
/// Pad this content at the sides.
|
||||
pub fn padded(self, padding: Sides<Rel<Length>>) -> Self {
|
||||
PadElem::new(self)
|
||||
.with_left(padding.left)
|
||||
.with_top(padding.top)
|
||||
.with_right(padding.right)
|
||||
.with_bottom(padding.bottom)
|
||||
.pack()
|
||||
}
|
||||
|
||||
/// Transform this content's contents without affecting layout.
|
||||
pub fn moved(self, delta: Axes<Rel<Length>>) -> Self {
|
||||
MoveElem::new(self).with_dx(delta.x).with_dy(delta.y).pack()
|
||||
}
|
||||
}
|
||||
|
||||
#[scope]
|
||||
impl Content {
|
||||
/// The content's element function. This function can be used to create the element
|
||||
@ -690,7 +742,7 @@ impl Repr for SequenceElem {
|
||||
} else {
|
||||
eco_format!(
|
||||
"[{}]",
|
||||
repr::pretty_array_like(
|
||||
crate::foundations::repr::pretty_array_like(
|
||||
&self.children.iter().map(|c| c.0.repr()).collect::<Vec<_>>(),
|
||||
false
|
||||
)
|
||||
@ -720,21 +772,6 @@ impl Repr for StyledElem {
|
||||
}
|
||||
}
|
||||
|
||||
/// Hosts metadata and ensures metadata is produced even for empty elements.
|
||||
#[elem(Behave)]
|
||||
pub struct MetaElem {
|
||||
/// Metadata that should be attached to all elements affected by this style
|
||||
/// property.
|
||||
#[fold]
|
||||
pub data: SmallVec<[Meta; 1]>,
|
||||
}
|
||||
|
||||
impl Behave for MetaElem {
|
||||
fn behaviour(&self) -> Behaviour {
|
||||
Behaviour::Invisible
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to extract the plain-text representation of the element.
|
||||
pub trait PlainText {
|
||||
/// Write this element's plain text into the given buffer.
|
||||
@ -743,7 +780,7 @@ pub trait PlainText {
|
||||
|
||||
/// The missing field access error message.
|
||||
#[cold]
|
||||
pub fn missing_field(field: &str) -> EcoString {
|
||||
fn missing_field(field: &str) -> EcoString {
|
||||
eco_format!("content does not contain field {}", field.repr())
|
||||
}
|
||||
|
||||
@ -756,66 +793,3 @@ fn missing_field_no_default(field: &str) -> EcoString {
|
||||
field.repr()
|
||||
)
|
||||
}
|
||||
|
||||
/// Fat pointer handling.
|
||||
///
|
||||
/// This assumes the memory representation of fat pointers. Although it is not
|
||||
/// guaranteed by Rust, it's improbable that it will change. Still, when the
|
||||
/// pointer metadata APIs are stable, we should definitely move to them:
|
||||
/// <https://github.com/rust-lang/rust/issues/81513>
|
||||
pub mod fat {
|
||||
use std::alloc::Layout;
|
||||
use std::mem;
|
||||
|
||||
/// Create a fat pointer from a data address and a vtable address.
|
||||
///
|
||||
/// # Safety
|
||||
/// Must only be called when `T` is a `dyn Trait`. The data address must point
|
||||
/// to a value whose type implements the trait of `T` and the `vtable` must have
|
||||
/// been extracted with [`vtable`].
|
||||
#[track_caller]
|
||||
pub unsafe fn from_raw_parts<T: ?Sized>(
|
||||
data: *const (),
|
||||
vtable: *const (),
|
||||
) -> *const T {
|
||||
let fat = FatPointer { data, vtable };
|
||||
debug_assert_eq!(Layout::new::<*const T>(), Layout::new::<FatPointer>());
|
||||
mem::transmute_copy::<FatPointer, *const T>(&fat)
|
||||
}
|
||||
|
||||
/// Create a mutable fat pointer from a data address and a vtable address.
|
||||
///
|
||||
/// # Safety
|
||||
/// Must only be called when `T` is a `dyn Trait`. The data address must point
|
||||
/// to a value whose type implements the trait of `T` and the `vtable` must have
|
||||
/// been extracted with [`vtable`].
|
||||
#[track_caller]
|
||||
pub unsafe fn from_raw_parts_mut<T: ?Sized>(
|
||||
data: *mut (),
|
||||
vtable: *const (),
|
||||
) -> *mut T {
|
||||
let fat = FatPointer { data, vtable };
|
||||
debug_assert_eq!(Layout::new::<*mut T>(), Layout::new::<FatPointer>());
|
||||
mem::transmute_copy::<FatPointer, *mut T>(&fat)
|
||||
}
|
||||
|
||||
/// Extract the address to a trait object's vtable.
|
||||
///
|
||||
/// # Safety
|
||||
/// Must only be called when `T` is a `dyn Trait`.
|
||||
#[track_caller]
|
||||
pub unsafe fn vtable<T: ?Sized>(ptr: *const T) -> *const () {
|
||||
debug_assert_eq!(Layout::new::<*const T>(), Layout::new::<FatPointer>());
|
||||
mem::transmute_copy::<*const T, FatPointer>(&ptr).vtable
|
||||
}
|
||||
|
||||
/// The memory representation of a trait object pointer.
|
||||
///
|
||||
/// Although this is not guaranteed by Rust, it's improbable that it will
|
||||
/// change.
|
||||
#[repr(C)]
|
||||
struct FatPointer {
|
||||
data: *const (),
|
||||
vtable: *const (),
|
||||
}
|
||||
}
|
@ -1,6 +1,4 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
use std::ops::{Add, Sub};
|
||||
|
||||
@ -10,8 +8,9 @@ use time::macros::format_description;
|
||||
use time::{format_description, Month, PrimitiveDateTime};
|
||||
|
||||
use crate::diag::{bail, StrResult};
|
||||
use crate::eval::{
|
||||
cast, func, repr, scope, ty, Dict, Duration, Repr, Smart, Str, Value, Vm,
|
||||
use crate::eval::Vm;
|
||||
use crate::foundations::{
|
||||
cast, func, repr, scope, ty, Dict, Duration, Repr, Smart, Str, Value,
|
||||
};
|
||||
use crate::World;
|
||||
|
||||
@ -238,26 +237,26 @@ impl Datetime {
|
||||
pub fn construct(
|
||||
/// The year of the datetime.
|
||||
#[named]
|
||||
year: Option<YearComponent>,
|
||||
year: Option<i32>,
|
||||
/// The month of the datetime.
|
||||
#[named]
|
||||
month: Option<MonthComponent>,
|
||||
month: Option<Month>,
|
||||
/// The day of the datetime.
|
||||
#[named]
|
||||
day: Option<DayComponent>,
|
||||
day: Option<u8>,
|
||||
/// The hour of the datetime.
|
||||
#[named]
|
||||
hour: Option<HourComponent>,
|
||||
hour: Option<u8>,
|
||||
/// The minute of the datetime.
|
||||
#[named]
|
||||
minute: Option<MinuteComponent>,
|
||||
minute: Option<u8>,
|
||||
/// The second of the datetime.
|
||||
#[named]
|
||||
second: Option<SecondComponent>,
|
||||
second: Option<u8>,
|
||||
) -> StrResult<Datetime> {
|
||||
let time = match (hour, minute, second) {
|
||||
(Some(hour), Some(minute), Some(second)) => {
|
||||
match time::Time::from_hms(hour.0, minute.0, second.0) {
|
||||
match time::Time::from_hms(hour, minute, second) {
|
||||
Ok(time) => Some(time),
|
||||
Err(_) => bail!("time is invalid"),
|
||||
}
|
||||
@ -268,7 +267,7 @@ impl Datetime {
|
||||
|
||||
let date = match (year, month, day) {
|
||||
(Some(year), Some(month), Some(day)) => {
|
||||
match time::Date::from_calendar_date(year.0, month.0, day.0) {
|
||||
match time::Date::from_calendar_date(year, month, day) {
|
||||
Ok(date) => Some(date),
|
||||
Err(_) => bail!("date is invalid"),
|
||||
}
|
||||
@ -493,43 +492,6 @@ impl Sub for Datetime {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct YearComponent(i32);
|
||||
pub struct MonthComponent(Month);
|
||||
pub struct DayComponent(u8);
|
||||
pub struct HourComponent(u8);
|
||||
pub struct MinuteComponent(u8);
|
||||
pub struct SecondComponent(u8);
|
||||
|
||||
cast! {
|
||||
YearComponent,
|
||||
v: i32 => Self(v),
|
||||
}
|
||||
|
||||
cast! {
|
||||
MonthComponent,
|
||||
v: u8 => Self(Month::try_from(v).map_err(|_| "month is invalid")?)
|
||||
}
|
||||
|
||||
cast! {
|
||||
DayComponent,
|
||||
v: u8 => Self(v),
|
||||
}
|
||||
|
||||
cast! {
|
||||
HourComponent,
|
||||
v: u8 => Self(v),
|
||||
}
|
||||
|
||||
cast! {
|
||||
MinuteComponent,
|
||||
v: u8 => Self(v),
|
||||
}
|
||||
|
||||
cast! {
|
||||
SecondComponent,
|
||||
v: u8 => Self(v),
|
||||
}
|
||||
|
||||
/// A format in which a datetime can be displayed.
|
||||
pub struct DisplayPattern(Str, format_description::OwnedFormatItem);
|
||||
|
||||
@ -543,6 +505,11 @@ cast! {
|
||||
}
|
||||
}
|
||||
|
||||
cast! {
|
||||
Month,
|
||||
v: u8 => Self::try_from(v).map_err(|_| "month is invalid")?
|
||||
}
|
||||
|
||||
/// Format the `Format` error of the time crate in an appropriate way.
|
||||
fn format_time_format_error(error: Format) -> EcoString {
|
||||
match error {
|
@ -8,7 +8,7 @@ use indexmap::IndexMap;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use crate::diag::StrResult;
|
||||
use crate::eval::{array, func, repr, scope, ty, Array, Repr, Str, Value};
|
||||
use crate::foundations::{array, func, repr, scope, ty, Array, Repr, Str, Value};
|
||||
use crate::syntax::is_ident;
|
||||
use crate::util::ArcExt;
|
||||
|
||||
@ -18,9 +18,9 @@ use crate::util::ArcExt;
|
||||
macro_rules! __dict {
|
||||
($($key:expr => $value:expr),* $(,)?) => {{
|
||||
#[allow(unused_mut)]
|
||||
let mut map = $crate::eval::IndexMap::new();
|
||||
$(map.insert($key.into(), $crate::eval::IntoValue::into_value($value));)*
|
||||
$crate::eval::Dict::from(map)
|
||||
let mut map = $crate::foundations::IndexMap::new();
|
||||
$(map.insert($key.into(), $crate::foundations::IntoValue::into_value($value));)*
|
||||
$crate::foundations::Dict::from(map)
|
||||
}};
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
use ecow::{eco_format, EcoString};
|
||||
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::ops::{Add, Div, Mul, Neg, Sub};
|
||||
|
||||
use ecow::{eco_format, EcoString};
|
||||
use time::ext::NumericalDuration;
|
||||
|
||||
use crate::eval::{func, repr, scope, ty, Repr};
|
||||
use crate::foundations::{func, repr, scope, ty, Repr};
|
||||
|
||||
/// Represents a positive or negative span of time.
|
||||
#[ty(scope)]
|
@ -1,4 +1,5 @@
|
||||
use std::any::{Any, TypeId};
|
||||
use std::borrow::Cow;
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::{self, Debug};
|
||||
use std::hash::Hasher;
|
||||
@ -8,14 +9,21 @@ use ecow::EcoString;
|
||||
use once_cell::sync::Lazy;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use super::{Content, Selector, Styles};
|
||||
use crate::diag::{SourceResult, StrResult};
|
||||
use crate::doc::{Lang, Region};
|
||||
use crate::eval::{cast, Args, Dict, Func, ParamInfo, Repr, Scope, Value, Vm};
|
||||
use crate::model::{Guard, Label, Location};
|
||||
use crate::eval::Vm;
|
||||
use crate::foundations::{
|
||||
cast, Args, Content, Dict, Func, Label, ParamInfo, Repr, Scope, Selector, StyleChain,
|
||||
Styles, Value,
|
||||
};
|
||||
use crate::introspection::Location;
|
||||
use crate::layout::Vt;
|
||||
use crate::syntax::Span;
|
||||
use crate::text::{Lang, Region};
|
||||
use crate::util::Static;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use typst_macros::elem;
|
||||
|
||||
/// A document element.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Element(Static<NativeElementData>);
|
||||
@ -93,8 +101,8 @@ impl Element {
|
||||
Selector::Elem(self, None)
|
||||
}
|
||||
|
||||
/// Create a selector for this element, filtering for those
|
||||
/// that [fields](super::Content::field) match the given argument.
|
||||
/// Create a selector for this element, filtering for those that
|
||||
/// [fields](crate::foundations::Content::field) match the given argument.
|
||||
pub fn where_(self, fields: SmallVec<[(u8, Value); 1]>) -> Selector {
|
||||
Selector::Elem(self, Some(fields))
|
||||
}
|
||||
@ -308,8 +316,67 @@ cast! {
|
||||
self => Element::from(self).into_value(),
|
||||
}
|
||||
|
||||
/// The named with which an element is referenced.
|
||||
pub trait LocalName {
|
||||
/// Get the name in the given language and (optionally) region.
|
||||
fn local_name(lang: Lang, region: Option<Region>) -> &'static str;
|
||||
/// Synthesize fields on an element. This happens before execution of any show
|
||||
/// rule.
|
||||
pub trait Synthesize {
|
||||
/// Prepare the element for show rule application.
|
||||
fn synthesize(&mut self, vt: &mut Vt, styles: StyleChain) -> SourceResult<()>;
|
||||
}
|
||||
|
||||
/// The base recipe for an element.
|
||||
pub trait Show {
|
||||
/// Execute the base recipe for this element.
|
||||
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content>;
|
||||
}
|
||||
|
||||
/// Post-process an element after it was realized.
|
||||
pub trait Finalize {
|
||||
/// Finalize the fully realized form of the element. Use this for effects
|
||||
/// that should work even in the face of a user-defined show rule.
|
||||
fn finalize(&self, realized: Content, styles: StyleChain) -> Content;
|
||||
}
|
||||
|
||||
/// How the element interacts with other elements.
|
||||
pub trait Behave {
|
||||
/// The element's interaction behaviour.
|
||||
fn behaviour(&self) -> Behaviour;
|
||||
|
||||
/// Whether this weak element is larger than a previous one and thus picked
|
||||
/// as the maximum when the levels are the same.
|
||||
#[allow(unused_variables)]
|
||||
fn larger(
|
||||
&self,
|
||||
prev: &(Cow<Content>, Behaviour, StyleChain),
|
||||
styles: StyleChain,
|
||||
) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// How an element interacts with other elements in a stream.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum Behaviour {
|
||||
/// A weak element which only survives when a supportive element is before
|
||||
/// and after it. Furthermore, per consecutive run of weak elements, only
|
||||
/// one survives: The one with the lowest weakness level (or the larger one
|
||||
/// if there is a tie).
|
||||
Weak(usize),
|
||||
/// An element that enables adjacent weak elements to exist. The default.
|
||||
Supportive,
|
||||
/// An element that destroys adjacent weak elements.
|
||||
Destructive,
|
||||
/// An element that does not interact at all with other elements, having the
|
||||
/// same effect as if it didn't exist, but has a visual representation.
|
||||
Ignorant,
|
||||
/// An element that does not have a visual representation.
|
||||
Invisible,
|
||||
}
|
||||
|
||||
/// Guards content against being affected by the same show rule multiple times.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum Guard {
|
||||
/// The nth recipe from the top of the chain.
|
||||
Nth(usize),
|
||||
/// The [base recipe](Show) for a kind of element.
|
||||
Base(Element),
|
||||
}
|
@ -1,8 +1,11 @@
|
||||
//! Fields on values.
|
||||
|
||||
use ecow::{eco_format, EcoString};
|
||||
|
||||
use crate::diag::StrResult;
|
||||
use crate::eval::{IntoValue, Type, Value, Version};
|
||||
use crate::geom::{Align, Length, Rel, Stroke};
|
||||
use crate::foundations::{IntoValue, Type, Value, Version};
|
||||
use crate::layout::{Align, Length, Rel};
|
||||
use crate::visualize::Stroke;
|
||||
|
||||
/// Try to access a field on a value.
|
||||
///
|
@ -2,8 +2,8 @@ use std::num::ParseFloatError;
|
||||
|
||||
use ecow::{eco_format, EcoString};
|
||||
|
||||
use crate::eval::{cast, func, repr, scope, ty, Repr, Str};
|
||||
use crate::geom::Ratio;
|
||||
use crate::foundations::{cast, func, repr, scope, ty, Repr, Str};
|
||||
use crate::layout::Ratio;
|
||||
|
||||
/// A floating-point number.
|
||||
///
|
@ -1,22 +1,20 @@
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::sync::Arc;
|
||||
|
||||
use comemo::{Prehashed, Tracked, TrackedMut};
|
||||
use comemo::{Prehashed, TrackedMut};
|
||||
use ecow::{eco_format, EcoString};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::diag::{bail, HintedStrResult, SourceResult, StrResult};
|
||||
use crate::eval::{
|
||||
cast, scope, ty, Args, CastInfo, Eval, FlowEvent, IntoValue, Route, Scope, Scopes,
|
||||
Tracer, Type, Value, Vm,
|
||||
use crate::diag::{bail, SourceResult, StrResult};
|
||||
use crate::eval::{Route, Vm};
|
||||
use crate::foundations::{
|
||||
cast, repr, scope, ty, Args, CastInfo, Content, Element, IntoValue, Scope, Scopes,
|
||||
Selector, Type, Value,
|
||||
};
|
||||
use crate::model::{
|
||||
Content, DelayedErrors, Element, Introspector, Locator, Selector, Vt,
|
||||
};
|
||||
use crate::syntax::ast::{self, AstNode};
|
||||
use crate::syntax::{FileId, Span, SyntaxNode};
|
||||
use crate::introspection::Locator;
|
||||
use crate::layout::Vt;
|
||||
use crate::syntax::{ast, FileId, Span, SyntaxNode};
|
||||
use crate::util::Static;
|
||||
use crate::World;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use typst_macros::func;
|
||||
@ -276,9 +274,9 @@ impl Func {
|
||||
// Determine the route inside the closure.
|
||||
let fresh = Route::new(closure.file);
|
||||
let route = if vm.file.is_none() { fresh.track() } else { vm.route };
|
||||
|
||||
Closure::call(
|
||||
crate::eval::call_closure(
|
||||
self,
|
||||
closure,
|
||||
vm.world(),
|
||||
route,
|
||||
vm.vt.introspector,
|
||||
@ -396,7 +394,7 @@ impl Debug for Func {
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Repr for Func {
|
||||
impl repr::Repr for Func {
|
||||
fn repr(&self) -> EcoString {
|
||||
match self.name() {
|
||||
Some(name) => name.into(),
|
||||
@ -496,7 +494,7 @@ pub struct ParamInfo {
|
||||
|
||||
/// A user-defined closure.
|
||||
#[derive(Debug, Hash)]
|
||||
pub(super) struct Closure {
|
||||
pub struct Closure {
|
||||
/// The closure's syntax node. Must be castable to `ast::Closure`.
|
||||
pub node: SyntaxNode,
|
||||
/// The source file where the closure was defined.
|
||||
@ -516,116 +514,6 @@ impl Closure {
|
||||
.name()
|
||||
.map(|ident| ident.as_str())
|
||||
}
|
||||
|
||||
/// Call the function in the context with the arguments.
|
||||
#[comemo::memoize]
|
||||
#[tracing::instrument(skip_all)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn call(
|
||||
func: &Func,
|
||||
world: Tracked<dyn World + '_>,
|
||||
route: Tracked<Route>,
|
||||
introspector: Tracked<Introspector>,
|
||||
locator: Tracked<Locator>,
|
||||
delayed: TrackedMut<DelayedErrors>,
|
||||
tracer: TrackedMut<Tracer>,
|
||||
depth: usize,
|
||||
mut args: Args,
|
||||
) -> SourceResult<Value> {
|
||||
let Repr::Closure(this) = &func.repr else {
|
||||
panic!("`this` must be a closure");
|
||||
};
|
||||
let closure = this.node.cast::<ast::Closure>().unwrap();
|
||||
|
||||
// Don't leak the scopes from the call site. Instead, we use the scope
|
||||
// of captured variables we collected earlier.
|
||||
let mut scopes = Scopes::new(None);
|
||||
scopes.top = this.captured.clone();
|
||||
|
||||
// Prepare VT.
|
||||
let mut locator = Locator::chained(locator);
|
||||
let vt = Vt {
|
||||
world,
|
||||
introspector,
|
||||
locator: &mut locator,
|
||||
delayed,
|
||||
tracer,
|
||||
};
|
||||
|
||||
// Prepare VM.
|
||||
let mut vm = Vm::new(vt, route, this.file, scopes);
|
||||
vm.depth = depth;
|
||||
|
||||
// Provide the closure itself for recursive calls.
|
||||
if let Some(name) = closure.name() {
|
||||
vm.define(name, Value::Func(func.clone()));
|
||||
}
|
||||
|
||||
// Parse the arguments according to the parameter list.
|
||||
let num_pos_params = closure
|
||||
.params()
|
||||
.children()
|
||||
.filter(|p| matches!(p, ast::Param::Pos(_)))
|
||||
.count();
|
||||
let num_pos_args = args.to_pos().len();
|
||||
let sink_size = num_pos_args.checked_sub(num_pos_params);
|
||||
|
||||
let mut sink = None;
|
||||
let mut sink_pos_values = None;
|
||||
let mut defaults = this.defaults.iter();
|
||||
for p in closure.params().children() {
|
||||
match p {
|
||||
ast::Param::Pos(pattern) => match pattern {
|
||||
ast::Pattern::Normal(ast::Expr::Ident(ident)) => {
|
||||
vm.define(ident, args.expect::<Value>(&ident)?)
|
||||
}
|
||||
ast::Pattern::Normal(_) => unreachable!(),
|
||||
pattern => {
|
||||
super::define_pattern(
|
||||
&mut vm,
|
||||
pattern,
|
||||
args.expect::<Value>("pattern parameter")?,
|
||||
)?;
|
||||
}
|
||||
},
|
||||
ast::Param::Sink(ident) => {
|
||||
sink = ident.name();
|
||||
if let Some(sink_size) = sink_size {
|
||||
sink_pos_values = Some(args.consume(sink_size)?);
|
||||
}
|
||||
}
|
||||
ast::Param::Named(named) => {
|
||||
let name = named.name();
|
||||
let default = defaults.next().unwrap();
|
||||
let value =
|
||||
args.named::<Value>(&name)?.unwrap_or_else(|| default.clone());
|
||||
vm.define(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(sink) = sink {
|
||||
let mut remaining_args = args.take();
|
||||
if let Some(sink_pos_values) = sink_pos_values {
|
||||
remaining_args.items.extend(sink_pos_values);
|
||||
}
|
||||
vm.define(sink, remaining_args);
|
||||
}
|
||||
|
||||
// Ensure all arguments have been used.
|
||||
args.finish()?;
|
||||
|
||||
// Handle control flow.
|
||||
let output = closure.body().eval(&mut vm)?;
|
||||
match vm.flow {
|
||||
Some(FlowEvent::Return(_, Some(explicit))) => return Ok(explicit),
|
||||
Some(FlowEvent::Return(_, None)) => {}
|
||||
Some(flow) => bail!(flow.forbidden()),
|
||||
None => {}
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Closure> for Func {
|
||||
@ -638,233 +526,3 @@ cast! {
|
||||
Closure,
|
||||
self => Value::Func(self.into()),
|
||||
}
|
||||
|
||||
/// A visitor that determines which variables to capture for a closure.
|
||||
pub struct CapturesVisitor<'a> {
|
||||
external: Option<&'a Scopes<'a>>,
|
||||
internal: Scopes<'a>,
|
||||
captures: Scope,
|
||||
}
|
||||
|
||||
impl<'a> CapturesVisitor<'a> {
|
||||
/// Create a new visitor for the given external scopes.
|
||||
pub fn new(external: Option<&'a Scopes<'a>>) -> Self {
|
||||
Self {
|
||||
external,
|
||||
internal: Scopes::new(None),
|
||||
captures: Scope::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the scope of captured variables.
|
||||
pub fn finish(self) -> Scope {
|
||||
self.captures
|
||||
}
|
||||
|
||||
/// Visit any node and collect all captured variables.
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn visit(&mut self, node: &SyntaxNode) {
|
||||
match node.cast() {
|
||||
// Every identifier is a potential variable that we need to capture.
|
||||
// Identifiers that shouldn't count as captures because they
|
||||
// actually bind a new name are handled below (individually through
|
||||
// the expressions that contain them).
|
||||
Some(ast::Expr::Ident(ident)) => self.capture(&ident, Scopes::get),
|
||||
Some(ast::Expr::MathIdent(ident)) => {
|
||||
self.capture(&ident, Scopes::get_in_math)
|
||||
}
|
||||
|
||||
// Code and content blocks create a scope.
|
||||
Some(ast::Expr::Code(_) | ast::Expr::Content(_)) => {
|
||||
self.internal.enter();
|
||||
for child in node.children() {
|
||||
self.visit(child);
|
||||
}
|
||||
self.internal.exit();
|
||||
}
|
||||
|
||||
// Don't capture the field of a field access.
|
||||
Some(ast::Expr::FieldAccess(access)) => {
|
||||
self.visit(access.target().to_untyped());
|
||||
}
|
||||
|
||||
// A closure contains parameter bindings, which are bound before the
|
||||
// body is evaluated. Care must be taken so that the default values
|
||||
// of named parameters cannot access previous parameter bindings.
|
||||
Some(ast::Expr::Closure(expr)) => {
|
||||
for param in expr.params().children() {
|
||||
if let ast::Param::Named(named) = param {
|
||||
self.visit(named.expr().to_untyped());
|
||||
}
|
||||
}
|
||||
|
||||
self.internal.enter();
|
||||
if let Some(name) = expr.name() {
|
||||
self.bind(name);
|
||||
}
|
||||
|
||||
for param in expr.params().children() {
|
||||
match param {
|
||||
ast::Param::Pos(pattern) => {
|
||||
for ident in pattern.idents() {
|
||||
self.bind(ident);
|
||||
}
|
||||
}
|
||||
ast::Param::Named(named) => self.bind(named.name()),
|
||||
ast::Param::Sink(spread) => {
|
||||
self.bind(spread.name().unwrap_or_default())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.visit(expr.body().to_untyped());
|
||||
self.internal.exit();
|
||||
}
|
||||
|
||||
// A let expression contains a binding, but that binding is only
|
||||
// active after the body is evaluated.
|
||||
Some(ast::Expr::Let(expr)) => {
|
||||
if let Some(init) = expr.init() {
|
||||
self.visit(init.to_untyped());
|
||||
}
|
||||
|
||||
for ident in expr.kind().idents() {
|
||||
self.bind(ident);
|
||||
}
|
||||
}
|
||||
|
||||
// A for loop contains one or two bindings in its pattern. These are
|
||||
// active after the iterable is evaluated but before the body is
|
||||
// evaluated.
|
||||
Some(ast::Expr::For(expr)) => {
|
||||
self.visit(expr.iter().to_untyped());
|
||||
self.internal.enter();
|
||||
|
||||
let pattern = expr.pattern();
|
||||
for ident in pattern.idents() {
|
||||
self.bind(ident);
|
||||
}
|
||||
|
||||
self.visit(expr.body().to_untyped());
|
||||
self.internal.exit();
|
||||
}
|
||||
|
||||
// An import contains items, but these are active only after the
|
||||
// path is evaluated.
|
||||
Some(ast::Expr::Import(expr)) => {
|
||||
self.visit(expr.source().to_untyped());
|
||||
if let Some(ast::Imports::Items(items)) = expr.imports() {
|
||||
for item in items.iter() {
|
||||
self.bind(item.bound_name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
// Never capture the name part of a named pair.
|
||||
if let Some(named) = node.cast::<ast::Named>() {
|
||||
self.visit(named.expr().to_untyped());
|
||||
return;
|
||||
}
|
||||
|
||||
// Everything else is traversed from left to right.
|
||||
for child in node.children() {
|
||||
self.visit(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Bind a new internal variable.
|
||||
fn bind(&mut self, ident: ast::Ident) {
|
||||
self.internal.top.define(ident.get().clone(), Value::None);
|
||||
}
|
||||
|
||||
/// Capture a variable if it isn't internal.
|
||||
#[inline]
|
||||
fn capture(
|
||||
&mut self,
|
||||
ident: &str,
|
||||
getter: impl FnOnce(&'a Scopes<'a>, &str) -> HintedStrResult<&'a Value>,
|
||||
) {
|
||||
if self.internal.get(ident).is_err() {
|
||||
let Some(value) = self
|
||||
.external
|
||||
.map(|external| getter(external, ident).ok())
|
||||
.unwrap_or(Some(&Value::None))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.captures.define_captured(ident, value.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::syntax::parse;
|
||||
|
||||
#[track_caller]
|
||||
fn test(text: &str, result: &[&str]) {
|
||||
let mut scopes = Scopes::new(None);
|
||||
scopes.top.define("f", 0);
|
||||
scopes.top.define("x", 0);
|
||||
scopes.top.define("y", 0);
|
||||
scopes.top.define("z", 0);
|
||||
|
||||
let mut visitor = CapturesVisitor::new(Some(&scopes));
|
||||
let root = parse(text);
|
||||
visitor.visit(&root);
|
||||
|
||||
let captures = visitor.finish();
|
||||
let mut names: Vec<_> = captures.iter().map(|(k, _)| k).collect();
|
||||
names.sort();
|
||||
|
||||
assert_eq!(names, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_captures() {
|
||||
// Let binding and function definition.
|
||||
test("#let x = x", &["x"]);
|
||||
test("#let x; #(x + y)", &["y"]);
|
||||
test("#let f(x, y) = x + y", &[]);
|
||||
test("#let f(x, y) = f", &[]);
|
||||
test("#let f = (x, y) => f", &["f"]);
|
||||
|
||||
// Closure with different kinds of params.
|
||||
test("#((x, y) => x + z)", &["z"]);
|
||||
test("#((x: y, z) => x + z)", &["y"]);
|
||||
test("#((..x) => x + y)", &["y"]);
|
||||
test("#((x, y: x + z) => x + y)", &["x", "z"]);
|
||||
test("#{x => x; x}", &["x"]);
|
||||
|
||||
// Show rule.
|
||||
test("#show y: x => x", &["y"]);
|
||||
test("#show y: x => x + z", &["y", "z"]);
|
||||
test("#show x: x => x", &["x"]);
|
||||
|
||||
// For loop.
|
||||
test("#for x in y { x + z }", &["y", "z"]);
|
||||
test("#for (x, y) in y { x + y }", &["y"]);
|
||||
test("#for x in y {} #x", &["x", "y"]);
|
||||
|
||||
// Import.
|
||||
test("#import z: x, y", &["z"]);
|
||||
test("#import x + y: x, y, z", &["x", "y"]);
|
||||
|
||||
// Blocks.
|
||||
test("#{ let x = 1; { let y = 2; y }; x + y }", &["y"]);
|
||||
test("#[#let x = 1]#x", &["x"]);
|
||||
|
||||
// Field access.
|
||||
test("#foo(body: 1)", &[]);
|
||||
test("#(body: 1)", &[]);
|
||||
test("#(body = 1)", &[]);
|
||||
test("#(body += y)", &["y"]);
|
||||
test("#{ (body, a) = (y, 1) }", &["y"]);
|
||||
test("#(x.at(y) = 5)", &["x", "y"])
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ use std::num::{NonZeroI64, NonZeroIsize, NonZeroU64, NonZeroUsize, ParseIntError
|
||||
|
||||
use ecow::{eco_format, EcoString};
|
||||
|
||||
use crate::eval::{cast, func, repr, scope, ty, Repr, Str, Value};
|
||||
use crate::foundations::{cast, func, repr, scope, ty, Repr, Str, Value};
|
||||
|
||||
/// A whole number.
|
||||
///
|
@ -1,8 +1,6 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use ecow::{eco_format, EcoString};
|
||||
|
||||
use crate::eval::{func, scope, ty, Repr};
|
||||
use crate::foundations::{func, scope, ty, Repr};
|
||||
use crate::util::PicoStr;
|
||||
|
||||
/// A label for an element.
|
@ -1,19 +1,9 @@
|
||||
//! Handles special built-in methods on values.
|
||||
|
||||
use crate::diag::{At, SourceResult};
|
||||
use crate::eval::{Args, Array, Dict, Str, Type, Value};
|
||||
use crate::foundations::{Args, Array, Dict, Str, Type, Value};
|
||||
use crate::syntax::Span;
|
||||
|
||||
/// Whether a specific method is mutating.
|
||||
pub fn is_mutating(method: &str) -> bool {
|
||||
matches!(method, "push" | "pop" | "insert" | "remove")
|
||||
}
|
||||
|
||||
/// Whether a specific method is an accessor.
|
||||
pub fn is_accessor(method: &str) -> bool {
|
||||
matches!(method, "first" | "last" | "at")
|
||||
}
|
||||
|
||||
/// List the available methods for a type and whether they take arguments.
|
||||
pub fn mutable_methods_on(ty: Type) -> &'static [(&'static str, bool)] {
|
||||
if ty == Type::of::<Array>() {
|
||||
@ -33,8 +23,18 @@ pub fn mutable_methods_on(ty: Type) -> &'static [(&'static str, bool)] {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a specific method is mutating.
|
||||
pub(crate) fn is_mutating_method(method: &str) -> bool {
|
||||
matches!(method, "push" | "pop" | "insert" | "remove")
|
||||
}
|
||||
|
||||
/// Whether a specific method is an accessor.
|
||||
pub(crate) fn is_accessor_method(method: &str) -> bool {
|
||||
matches!(method, "first" | "last" | "at")
|
||||
}
|
||||
|
||||
/// Call a mutating method on a value.
|
||||
pub fn call_mut(
|
||||
pub(crate) fn call_method_mut(
|
||||
value: &mut Value,
|
||||
method: &str,
|
||||
mut args: Args,
|
||||
@ -76,7 +76,7 @@ pub fn call_mut(
|
||||
}
|
||||
|
||||
/// Call an accessor method on a value.
|
||||
pub fn call_access<'a>(
|
||||
pub(crate) fn call_method_access<'a>(
|
||||
value: &'a mut Value,
|
||||
method: &str,
|
||||
mut args: Args,
|
@ -1,16 +1,93 @@
|
||||
use typst::eval::{
|
||||
Datetime, Duration, EvalMode, Module, Never, NoneValue, Plugin, Regex, Repr, Version,
|
||||
//! Foundational types and functions.
|
||||
|
||||
pub mod calc;
|
||||
pub mod repr;
|
||||
pub mod sys;
|
||||
|
||||
mod args;
|
||||
mod array;
|
||||
mod auto;
|
||||
mod bool;
|
||||
mod bytes;
|
||||
mod cast;
|
||||
mod content;
|
||||
mod datetime;
|
||||
mod dict;
|
||||
mod duration;
|
||||
mod element;
|
||||
mod fields;
|
||||
mod float;
|
||||
mod func;
|
||||
mod int;
|
||||
mod label;
|
||||
mod methods;
|
||||
mod module;
|
||||
mod none;
|
||||
mod plugin;
|
||||
mod scope;
|
||||
mod selector;
|
||||
mod str;
|
||||
mod styles;
|
||||
mod ty;
|
||||
mod value;
|
||||
mod version;
|
||||
|
||||
pub use self::args::*;
|
||||
pub use self::array::*;
|
||||
pub use self::auto::*;
|
||||
pub use self::bytes::*;
|
||||
pub use self::cast::*;
|
||||
pub use self::content::*;
|
||||
pub use self::datetime::*;
|
||||
pub use self::dict::*;
|
||||
pub use self::duration::*;
|
||||
pub use self::element::*;
|
||||
pub use self::fields::*;
|
||||
pub use self::float::*;
|
||||
pub use self::func::*;
|
||||
pub use self::int::*;
|
||||
pub use self::label::*;
|
||||
pub use self::methods::*;
|
||||
pub use self::module::*;
|
||||
pub use self::none::*;
|
||||
pub use self::plugin::*;
|
||||
pub use self::repr::Repr;
|
||||
pub use self::scope::*;
|
||||
pub use self::selector::*;
|
||||
pub use self::str::*;
|
||||
pub use self::styles::*;
|
||||
pub use self::ty::*;
|
||||
pub use self::value::*;
|
||||
pub use self::version::*;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use {
|
||||
ecow::{eco_format, eco_vec},
|
||||
indexmap::IndexMap,
|
||||
once_cell::sync::Lazy,
|
||||
};
|
||||
|
||||
use crate::prelude::*;
|
||||
use ecow::EcoString;
|
||||
|
||||
/// Hook up all foundational definitions.
|
||||
use crate::diag::{bail, SourceResult, StrResult};
|
||||
use crate::eval::{EvalMode, Vm};
|
||||
use crate::syntax::Spanned;
|
||||
|
||||
/// Foundational types and functions.
|
||||
///
|
||||
/// Here, you'll find documentation for basic data types like [integers]($int)
|
||||
/// and [strings]($str) as well as details about core computational functions.
|
||||
#[category]
|
||||
pub static FOUNDATIONS: Category;
|
||||
|
||||
/// Hook up all `foundations` definitions.
|
||||
pub(super) fn define(global: &mut Scope) {
|
||||
global.category("foundations");
|
||||
global.category(FOUNDATIONS);
|
||||
global.define_type::<bool>();
|
||||
global.define_type::<i64>();
|
||||
global.define_type::<f64>();
|
||||
global.define_type::<Str>();
|
||||
global.define_type::<Label>();
|
||||
global.define_type::<Bytes>();
|
||||
global.define_type::<Content>();
|
||||
global.define_type::<Array>();
|
||||
@ -20,38 +97,18 @@ pub(super) fn define(global: &mut Scope) {
|
||||
global.define_type::<Type>();
|
||||
global.define_type::<Module>();
|
||||
global.define_type::<Regex>();
|
||||
global.define_type::<Selector>();
|
||||
global.define_type::<Datetime>();
|
||||
global.define_type::<Duration>();
|
||||
global.define_type::<Version>();
|
||||
global.define_type::<Plugin>();
|
||||
global.define_func::<repr>();
|
||||
global.define_func::<repr::repr>();
|
||||
global.define_func::<panic>();
|
||||
global.define_func::<assert>();
|
||||
global.define_func::<eval>();
|
||||
}
|
||||
|
||||
/// Returns the string representation of a value.
|
||||
///
|
||||
/// When inserted into content, most values are displayed as this representation
|
||||
/// in monospace with syntax-highlighting. The exceptions are `{none}`,
|
||||
/// integers, floats, strings, content, and functions.
|
||||
///
|
||||
/// **Note:** This function is for debugging purposes. Its output should not be
|
||||
/// considered stable and may change at any time!
|
||||
///
|
||||
/// # Example
|
||||
/// ```example
|
||||
/// #none vs #repr(none) \
|
||||
/// #"hello" vs #repr("hello") \
|
||||
/// #(1, 2) vs #repr((1, 2)) \
|
||||
/// #[*Hi*] vs #repr([*Hi*])
|
||||
/// ```
|
||||
#[func(title = "Representation")]
|
||||
pub fn repr(
|
||||
/// The value whose string representation to produce.
|
||||
value: Value,
|
||||
) -> Str {
|
||||
value.repr().into()
|
||||
global.define_func::<style>();
|
||||
global.define_module(calc::module());
|
||||
global.define_module(sys::module());
|
||||
}
|
||||
|
||||
/// Fails with an error.
|
||||
@ -232,5 +289,5 @@ pub fn eval(
|
||||
for (key, value) in dict {
|
||||
scope.define(key, value);
|
||||
}
|
||||
typst::eval::eval_string(vm.world(), &text, span, mode, scope)
|
||||
crate::eval::eval_string(vm.world(), &text, span, mode, scope)
|
||||
}
|
@ -4,7 +4,7 @@ use std::sync::Arc;
|
||||
use ecow::{eco_format, EcoString};
|
||||
|
||||
use crate::diag::StrResult;
|
||||
use crate::eval::{ty, Content, Scope, Value};
|
||||
use crate::foundations::{repr, ty, Content, Scope, Value};
|
||||
|
||||
/// An evaluated module, either built-in or resulting from a file.
|
||||
///
|
||||
@ -110,7 +110,7 @@ impl Debug for Module {
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Repr for Module {
|
||||
impl repr::Repr for Module {
|
||||
fn repr(&self) -> EcoString {
|
||||
eco_format!("<module {}>", self.name())
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
use ecow::EcoString;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
|
||||
use ecow::EcoString;
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
use crate::diag::StrResult;
|
||||
use crate::eval::{cast, ty, CastInfo, FromValue, IntoValue, Reflect, Repr, Type, Value};
|
||||
use crate::foundations::{
|
||||
cast, ty, CastInfo, FromValue, IntoValue, Reflect, Repr, Type, Value,
|
||||
};
|
||||
|
||||
/// A value that indicates the absence of any other value.
|
||||
///
|
@ -1,12 +1,13 @@
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use ecow::{eco_format, EcoString};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use wasmi::{AsContext, AsContextMut, Caller, Engine, Linker, Module};
|
||||
|
||||
use crate::diag::{bail, At, SourceResult, StrResult};
|
||||
use crate::eval::{func, scope, ty, Bytes, Vm};
|
||||
use crate::eval::Vm;
|
||||
use crate::foundations::{func, repr, scope, ty, Bytes};
|
||||
use crate::syntax::Spanned;
|
||||
use crate::World;
|
||||
|
||||
@ -303,7 +304,7 @@ impl Debug for Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Repr for Plugin {
|
||||
impl repr::Repr for Plugin {
|
||||
fn repr(&self) -> EcoString {
|
||||
"plugin(..)".into()
|
||||
}
|
@ -1,7 +1,36 @@
|
||||
//! Debug representation of values.
|
||||
|
||||
use ecow::{eco_format, EcoString};
|
||||
|
||||
use crate::foundations::{func, Str, Value};
|
||||
|
||||
/// The Unicode minus sign.
|
||||
pub const MINUS_SIGN: &str = "\u{2212}";
|
||||
|
||||
/// Returns the string representation of a value.
|
||||
///
|
||||
/// When inserted into content, most values are displayed as this representation
|
||||
/// in monospace with syntax-highlighting. The exceptions are `{none}`,
|
||||
/// integers, floats, strings, content, and functions.
|
||||
///
|
||||
/// **Note:** This function is for debugging purposes. Its output should not be
|
||||
/// considered stable and may change at any time!
|
||||
///
|
||||
/// # Example
|
||||
/// ```example
|
||||
/// #none vs #repr(none) \
|
||||
/// #"hello" vs #repr("hello") \
|
||||
/// #(1, 2) vs #repr((1, 2)) \
|
||||
/// #[*Hi*] vs #repr([*Hi*])
|
||||
/// ```
|
||||
#[func(title = "Representation")]
|
||||
pub fn repr(
|
||||
/// The value whose string representation to produce.
|
||||
value: Value,
|
||||
) -> Str {
|
||||
value.repr().into()
|
||||
}
|
||||
|
||||
/// A trait that defines the `repr` of a Typst value.
|
||||
pub trait Repr {
|
||||
/// Return the debug representation of the value.
|
@ -5,10 +5,15 @@ use ecow::{eco_format, EcoString};
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use crate::diag::{bail, HintedStrResult, HintedString, StrResult};
|
||||
use crate::eval::{
|
||||
Func, IntoValue, Library, Module, NativeFunc, NativeFuncData, NativeType, Type, Value,
|
||||
use crate::foundations::{
|
||||
Element, Func, IntoValue, Module, NativeElement, NativeFunc, NativeFuncData,
|
||||
NativeType, Type, Value,
|
||||
};
|
||||
use crate::model::{Element, NativeElement};
|
||||
use crate::util::Static;
|
||||
use crate::Library;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use typst_macros::category;
|
||||
|
||||
/// A stack of scopes.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
@ -97,7 +102,7 @@ fn unknown_variable(var: &str) -> HintedString {
|
||||
pub struct Scope {
|
||||
map: IndexMap<EcoString, Slot>,
|
||||
deduplicate: bool,
|
||||
category: Option<&'static str>,
|
||||
category: Option<Category>,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
@ -112,8 +117,8 @@ impl Scope {
|
||||
}
|
||||
|
||||
/// Enter a new category.
|
||||
pub fn category(&mut self, name: &'static str) {
|
||||
self.category = Some(name);
|
||||
pub fn category(&mut self, category: Category) {
|
||||
self.category = Some(category);
|
||||
}
|
||||
|
||||
/// Reset the category.
|
||||
@ -185,7 +190,7 @@ impl Scope {
|
||||
}
|
||||
|
||||
/// Get the category of a definition.
|
||||
pub fn get_category(&self, var: &str) -> Option<&'static str> {
|
||||
pub fn get_category(&self, var: &str) -> Option<Category> {
|
||||
self.map.get(var)?.category
|
||||
}
|
||||
|
||||
@ -215,6 +220,15 @@ impl Hash for Scope {
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines the associated scope of a Rust type.
|
||||
pub trait NativeScope {
|
||||
/// The constructor function for the type, if any.
|
||||
fn constructor() -> Option<&'static NativeFuncData>;
|
||||
|
||||
/// Get the associated scope for the type.
|
||||
fn scope() -> Scope;
|
||||
}
|
||||
|
||||
/// A slot where a value is stored.
|
||||
#[derive(Clone, Hash)]
|
||||
struct Slot {
|
||||
@ -223,7 +237,7 @@ struct Slot {
|
||||
/// The kind of slot, determines how the value can be accessed.
|
||||
kind: Kind,
|
||||
/// The category of the slot.
|
||||
category: Option<&'static str>,
|
||||
category: Option<Category>,
|
||||
}
|
||||
|
||||
/// The different kinds of slots.
|
||||
@ -237,7 +251,7 @@ enum Kind {
|
||||
|
||||
impl Slot {
|
||||
/// Create a new slot.
|
||||
fn new(value: Value, kind: Kind, category: Option<&'static str>) -> Self {
|
||||
fn new(value: Value, kind: Kind, category: Option<Category>) -> Self {
|
||||
Self { value, kind, category }
|
||||
}
|
||||
|
||||
@ -260,11 +274,42 @@ impl Slot {
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines the associated scope of a Rust type.
|
||||
pub trait NativeScope {
|
||||
/// The constructor function for the type, if any.
|
||||
fn constructor() -> Option<&'static NativeFuncData>;
|
||||
/// A group of related definitions.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Category(Static<CategoryData>);
|
||||
|
||||
/// Get the associated scope for the type.
|
||||
fn scope() -> Scope;
|
||||
impl Category {
|
||||
/// Create a new category from raw data.
|
||||
pub const fn from_data(data: &'static CategoryData) -> Self {
|
||||
Self(Static(data))
|
||||
}
|
||||
|
||||
/// The category's name.
|
||||
pub fn name(&self) -> &'static str {
|
||||
self.0.name
|
||||
}
|
||||
|
||||
/// The type's title case name, for use in documentation (e.g. `String`).
|
||||
pub fn title(&self) -> &'static str {
|
||||
self.0.title
|
||||
}
|
||||
|
||||
/// Documentation for the category.
|
||||
pub fn docs(&self) -> &'static str {
|
||||
self.0.docs
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Category {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "Category({})", self.name())
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines a category.
|
||||
#[derive(Debug)]
|
||||
pub struct CategoryData {
|
||||
pub name: &'static str,
|
||||
pub title: &'static str,
|
||||
pub docs: &'static str,
|
||||
}
|
@ -1,16 +1,17 @@
|
||||
use std::any::{Any, TypeId};
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
use ecow::{eco_format, EcoString, EcoVec};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::diag::{bail, StrResult};
|
||||
use crate::eval::{
|
||||
cast, func, item, repr, scope, ty, CastInfo, Dict, FromValue, Func, Reflect, Regex,
|
||||
Repr, Str, Symbol, Type, Value,
|
||||
use crate::foundations::{
|
||||
cast, func, repr, scope, ty, CastInfo, Content, Dict, Element, FromValue, Func,
|
||||
Label, Reflect, Regex, Repr, Str, Type, Value,
|
||||
};
|
||||
use crate::model::{Content, Element, Label, Locatable, Location};
|
||||
use crate::introspection::{Locatable, Location};
|
||||
use crate::symbols::Symbol;
|
||||
use crate::text::TextElem;
|
||||
|
||||
/// A helper macro to create a field selector used in [`Selector::Elem`]
|
||||
///
|
||||
@ -25,12 +26,12 @@ macro_rules! __select_where {
|
||||
let mut fields = ::smallvec::SmallVec::new();
|
||||
$(
|
||||
fields.push((
|
||||
<$ty as $crate::model::ElementFields>::Fields::$field as u8,
|
||||
$crate::eval::IntoValue::into_value($value),
|
||||
<$ty as $crate::foundations::ElementFields>::Fields::$field as u8,
|
||||
$crate::foundations::IntoValue::into_value($value),
|
||||
));
|
||||
)*
|
||||
$crate::model::Selector::Elem(
|
||||
<$ty as $crate::model::NativeElement>::elem(),
|
||||
$crate::foundations::Selector::Elem(
|
||||
<$ty as $crate::foundations::NativeElement>::elem(),
|
||||
Some(fields),
|
||||
)
|
||||
}};
|
||||
@ -138,10 +139,9 @@ impl Selector {
|
||||
.all(|(id, value)| target.get(*id).as_ref() == Some(value))
|
||||
}
|
||||
Self::Label(label) => target.label() == Some(*label),
|
||||
Self::Regex(regex) => {
|
||||
target.func() == item!(text_elem)
|
||||
&& item!(text_str)(target).map_or(false, |text| regex.is_match(text))
|
||||
}
|
||||
Self::Regex(regex) => target
|
||||
.to::<TextElem>()
|
||||
.map_or(false, |elem| regex.is_match(elem.text())),
|
||||
Self::Can(cap) => target.func().can_type_id(*cap),
|
||||
Self::Or(selectors) => selectors.iter().any(move |sel| sel.matches(target)),
|
||||
Self::And(selectors) => selectors.iter().all(move |sel| sel.matches(target)),
|
@ -8,12 +8,12 @@ use serde::{Deserialize, Serialize};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::diag::{bail, At, SourceResult, StrResult};
|
||||
use crate::eval::{
|
||||
cast, dict, func, repr, scope, ty, Args, Array, Bytes, Dict, Func, IntoValue, Repr,
|
||||
Type, Value, Version, Vm,
|
||||
use crate::eval::Vm;
|
||||
use crate::foundations::{
|
||||
cast, dict, func, repr, scope, ty, Args, Array, Bytes, Dict, Func, IntoValue, Label,
|
||||
Repr, Type, Value, Version,
|
||||
};
|
||||
use crate::geom::Align;
|
||||
use crate::model::Label;
|
||||
use crate::layout::Align;
|
||||
use crate::syntax::{Span, Spanned};
|
||||
|
||||
/// Create a new [`Str`] from a format string.
|
||||
@ -21,7 +21,7 @@ use crate::syntax::{Span, Spanned};
|
||||
#[doc(hidden)]
|
||||
macro_rules! __format_str {
|
||||
($($tts:tt)*) => {{
|
||||
$crate::eval::Str::from($crate::eval::eco_format!($($tts)*))
|
||||
$crate::foundations::Str::from($crate::foundations::eco_format!($($tts)*))
|
||||
}};
|
||||
}
|
||||
|
@ -2,9 +2,7 @@ use std::any::Any;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::iter;
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
use std::{iter, mem, ptr};
|
||||
|
||||
use comemo::Prehashed;
|
||||
use ecow::{eco_vec, EcoString, EcoVec};
|
||||
@ -12,9 +10,58 @@ use once_cell::sync::Lazy;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::diag::{SourceResult, Trace, Tracepoint};
|
||||
use crate::eval::{cast, ty, Args, Func, Repr, Value, Vm};
|
||||
use crate::model::{Content, Element, NativeElement, Selector, Vt};
|
||||
use crate::eval::Vm;
|
||||
use crate::foundations::{
|
||||
cast, elem, func, ty, Args, Content, Element, Func, NativeElement, Repr, Selector,
|
||||
Show, Value,
|
||||
};
|
||||
use crate::layout::Vt;
|
||||
use crate::syntax::Span;
|
||||
use crate::text::{FontFamily, FontList, TextElem};
|
||||
|
||||
/// Provides access to active styles.
|
||||
///
|
||||
/// The styles are currently opaque and only useful in combination with the
|
||||
/// [`measure`]($measure) function. See its documentation for more details. In
|
||||
/// the future, the provided styles might also be directly accessed to look up
|
||||
/// styles defined by [set rules]($styling/#set-rules).
|
||||
///
|
||||
/// ```example
|
||||
/// #let thing(body) = style(styles => {
|
||||
/// let size = measure(body, styles)
|
||||
/// [Width of "#body" is #size.width]
|
||||
/// })
|
||||
///
|
||||
/// #thing[Hey] \
|
||||
/// #thing[Welcome]
|
||||
/// ```
|
||||
#[func]
|
||||
pub fn style(
|
||||
/// A function to call with the styles. Its return value is displayed
|
||||
/// in the document.
|
||||
///
|
||||
/// This function is called once for each time the content returned by
|
||||
/// `style` appears in the document. That makes it possible to generate
|
||||
/// content that depends on the style context it appears in.
|
||||
func: Func,
|
||||
) -> Content {
|
||||
StyleElem::new(func).pack()
|
||||
}
|
||||
|
||||
/// Executes a style access.
|
||||
#[elem(Show)]
|
||||
struct StyleElem {
|
||||
/// The function to call with the styles.
|
||||
#[required]
|
||||
func: Func,
|
||||
}
|
||||
|
||||
impl Show for StyleElem {
|
||||
#[tracing::instrument(name = "StyleElem::show", skip_all)]
|
||||
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||
Ok(self.func().call_vt(vt, [styles.to_map()])?.display())
|
||||
}
|
||||
}
|
||||
|
||||
/// A list of style properties.
|
||||
#[ty]
|
||||
@ -83,6 +130,16 @@ impl Styles {
|
||||
Style::Recipe(recipe) => recipe.is_of(elem).then_some(Some(recipe.span)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Set a font family composed of a preferred family and existing families
|
||||
/// from a style chain.
|
||||
pub fn set_family(&mut self, preferred: FontFamily, existing: StyleChain) {
|
||||
self.set(TextElem::set_font(FontList(
|
||||
std::iter::once(preferred)
|
||||
.chain(TextElem::font_in(existing).into_iter().cloned())
|
||||
.collect(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Style> for Styles {
|
||||
@ -279,7 +336,7 @@ pub struct Recipe {
|
||||
/// Determines whether the recipe applies to an element.
|
||||
pub selector: Option<Selector>,
|
||||
/// The transformation to perform on the match.
|
||||
pub transform: Transform,
|
||||
pub transform: Transformation,
|
||||
}
|
||||
|
||||
impl Recipe {
|
||||
@ -301,8 +358,8 @@ impl Recipe {
|
||||
/// Apply the recipe to the given content.
|
||||
pub fn apply_vm(&self, vm: &mut Vm, content: Content) -> SourceResult<Content> {
|
||||
match &self.transform {
|
||||
Transform::Content(content) => Ok(content.clone()),
|
||||
Transform::Func(func) => {
|
||||
Transformation::Content(content) => Ok(content.clone()),
|
||||
Transformation::Func(func) => {
|
||||
let args = Args::new(self.span, [Value::Content(content.clone())]);
|
||||
let mut result = func.call_vm(vm, args);
|
||||
// For selector-less show rules, a tracepoint makes no sense.
|
||||
@ -312,15 +369,15 @@ impl Recipe {
|
||||
}
|
||||
Ok(result?.display())
|
||||
}
|
||||
Transform::Style(styles) => Ok(content.styled_with_map(styles.clone())),
|
||||
Transformation::Style(styles) => Ok(content.styled_with_map(styles.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply the recipe to the given content.
|
||||
pub fn apply_vt(&self, vt: &mut Vt, content: Content) -> SourceResult<Content> {
|
||||
match &self.transform {
|
||||
Transform::Content(content) => Ok(content.clone()),
|
||||
Transform::Func(func) => {
|
||||
Transformation::Content(content) => Ok(content.clone()),
|
||||
Transformation::Func(func) => {
|
||||
let mut result = func.call_vt(vt, [Value::Content(content.clone())]);
|
||||
if self.selector.is_some() {
|
||||
let point = || Tracepoint::Show(content.func().name().into());
|
||||
@ -328,7 +385,7 @@ impl Recipe {
|
||||
}
|
||||
Ok(result?.display())
|
||||
}
|
||||
Transform::Style(styles) => Ok(content.styled_with_map(styles.clone())),
|
||||
Transformation::Style(styles) => Ok(content.styled_with_map(styles.clone())),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -346,7 +403,7 @@ impl Debug for Recipe {
|
||||
|
||||
/// A show rule transformation that can be applied to a match.
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
pub enum Transform {
|
||||
pub enum Transformation {
|
||||
/// Replacement content.
|
||||
Content(Content),
|
||||
/// A function to apply to the match.
|
||||
@ -355,7 +412,7 @@ pub enum Transform {
|
||||
Style(Styles),
|
||||
}
|
||||
|
||||
impl Debug for Transform {
|
||||
impl Debug for Transformation {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Content(content) => content.fmt(f),
|
||||
@ -366,7 +423,7 @@ impl Debug for Transform {
|
||||
}
|
||||
|
||||
cast! {
|
||||
Transform,
|
||||
Transformation,
|
||||
content: Content => Self::Content(content),
|
||||
func: Func => Self::Func(func),
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user