mirror of
https://github.com/typst/typst
synced 2025-06-28 08:12:53 +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"
|
name = "typst"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"az",
|
||||||
"bitflags 2.4.1",
|
"bitflags 2.4.1",
|
||||||
|
"chinese-number",
|
||||||
|
"ciborium",
|
||||||
"comemo",
|
"comemo",
|
||||||
|
"csv",
|
||||||
"ecow",
|
"ecow",
|
||||||
"fontdb",
|
"fontdb",
|
||||||
|
"hayagriva",
|
||||||
|
"hypher",
|
||||||
|
"icu_properties",
|
||||||
|
"icu_provider",
|
||||||
|
"icu_provider_adapters",
|
||||||
|
"icu_provider_blob",
|
||||||
|
"icu_segmenter",
|
||||||
"image",
|
"image",
|
||||||
"indexmap 2.0.2",
|
"indexmap 2.0.2",
|
||||||
"kurbo",
|
"kurbo",
|
||||||
"lasso",
|
"lasso",
|
||||||
|
"lipsum",
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"palette",
|
"palette",
|
||||||
@ -2720,16 +2732,22 @@ dependencies = [
|
|||||||
"roxmltree",
|
"roxmltree",
|
||||||
"rustybuzz",
|
"rustybuzz",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_yaml 0.9.27",
|
||||||
"siphasher",
|
"siphasher",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"stacker",
|
"stacker",
|
||||||
|
"syntect",
|
||||||
"time",
|
"time",
|
||||||
"toml",
|
"toml",
|
||||||
"tracing",
|
"tracing",
|
||||||
"ttf-parser",
|
"ttf-parser",
|
||||||
|
"typed-arena",
|
||||||
"typst-macros",
|
"typst-macros",
|
||||||
"typst-syntax",
|
"typst-syntax",
|
||||||
|
"unicode-bidi",
|
||||||
"unicode-math-class",
|
"unicode-math-class",
|
||||||
|
"unicode-script",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"usvg",
|
"usvg",
|
||||||
"wasmi",
|
"wasmi",
|
||||||
@ -2771,7 +2789,6 @@ dependencies = [
|
|||||||
"tracing-flame",
|
"tracing-flame",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"typst",
|
"typst",
|
||||||
"typst-library",
|
|
||||||
"typst-pdf",
|
"typst-pdf",
|
||||||
"typst-render",
|
"typst-render",
|
||||||
"typst-svg",
|
"typst-svg",
|
||||||
@ -2795,7 +2812,6 @@ dependencies = [
|
|||||||
"syntect",
|
"syntect",
|
||||||
"typed-arena",
|
"typed-arena",
|
||||||
"typst",
|
"typst",
|
||||||
"typst-library",
|
|
||||||
"unicode_names2",
|
"unicode_names2",
|
||||||
"unscanny",
|
"unscanny",
|
||||||
"yaml-front-matter",
|
"yaml-front-matter",
|
||||||
@ -2814,46 +2830,6 @@ dependencies = [
|
|||||||
"unscanny",
|
"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]]
|
[[package]]
|
||||||
name = "typst-macros"
|
name = "typst-macros"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@ -2948,7 +2924,6 @@ dependencies = [
|
|||||||
"tiny-skia",
|
"tiny-skia",
|
||||||
"ttf-parser",
|
"ttf-parser",
|
||||||
"typst",
|
"typst",
|
||||||
"typst-library",
|
|
||||||
"typst-pdf",
|
"typst-pdf",
|
||||||
"typst-render",
|
"typst-render",
|
||||||
"typst-svg",
|
"typst-svg",
|
||||||
|
@ -20,7 +20,6 @@ typst = { path = "crates/typst" }
|
|||||||
typst-cli = { path = "crates/typst-cli" }
|
typst-cli = { path = "crates/typst-cli" }
|
||||||
typst-docs = { path = "crates/typst-docs" }
|
typst-docs = { path = "crates/typst-docs" }
|
||||||
typst-ide = { path = "crates/typst-ide" }
|
typst-ide = { path = "crates/typst-ide" }
|
||||||
typst-library = { path = "crates/typst-library" }
|
|
||||||
typst-macros = { path = "crates/typst-macros" }
|
typst-macros = { path = "crates/typst-macros" }
|
||||||
typst-pdf = { path = "crates/typst-pdf" }
|
typst-pdf = { path = "crates/typst-pdf" }
|
||||||
typst-render = { path = "crates/typst-render" }
|
typst-render = { path = "crates/typst-render" }
|
||||||
@ -98,7 +97,7 @@ tar = "0.4"
|
|||||||
tempfile = "3.7.0"
|
tempfile = "3.7.0"
|
||||||
time = { version = "0.3.20", features = ["formatting", "macros", "parsing"] }
|
time = { version = "0.3.20", features = ["formatting", "macros", "parsing"] }
|
||||||
tiny-skia = "0.11"
|
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 = "0.1.37"
|
||||||
tracing-error = "0.2"
|
tracing-error = "0.2"
|
||||||
tracing-flame = "0.2.0"
|
tracing-flame = "0.2.0"
|
||||||
|
@ -21,7 +21,6 @@ doc = false
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
typst = { workspace = true }
|
typst = { workspace = true }
|
||||||
typst-library = { workspace = true }
|
|
||||||
typst-pdf = { workspace = true }
|
typst-pdf = { workspace = true }
|
||||||
typst-render = { workspace = true }
|
typst-render = { workspace = true }
|
||||||
typst-svg = { workspace = true }
|
typst-svg = { workspace = true }
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use semver::Version;
|
|
||||||
|
|
||||||
use clap::{ArgAction, Args, Parser, Subcommand, ValueEnum};
|
use clap::{ArgAction, Args, Parser, Subcommand, ValueEnum};
|
||||||
|
use semver::Version;
|
||||||
|
|
||||||
/// The character typically used to separate path components
|
/// The character typically used to separate path components
|
||||||
/// in environment variables.
|
/// in environment variables.
|
||||||
|
@ -4,12 +4,14 @@ use std::path::{Path, PathBuf};
|
|||||||
use chrono::{Datelike, Timelike};
|
use chrono::{Datelike, Timelike};
|
||||||
use codespan_reporting::diagnostic::{Diagnostic, Label};
|
use codespan_reporting::diagnostic::{Diagnostic, Label};
|
||||||
use codespan_reporting::term::{self, termcolor};
|
use codespan_reporting::term::{self, termcolor};
|
||||||
|
use ecow::eco_format;
|
||||||
use termcolor::{ColorChoice, StandardStream};
|
use termcolor::{ColorChoice, StandardStream};
|
||||||
use typst::diag::{bail, Severity, SourceDiagnostic, StrResult};
|
use typst::diag::{bail, Severity, SourceDiagnostic, StrResult};
|
||||||
use typst::doc::Document;
|
use typst::eval::Tracer;
|
||||||
use typst::eval::{eco_format, Datetime, Tracer};
|
use typst::foundations::Datetime;
|
||||||
use typst::geom::Color;
|
use typst::model::Document;
|
||||||
use typst::syntax::{FileId, Source, Span};
|
use typst::syntax::{FileId, Source, Span};
|
||||||
|
use typst::visualize::Color;
|
||||||
use typst::{World, WorldExt};
|
use typst::{World, WorldExt};
|
||||||
|
|
||||||
use crate::args::{CompileCommand, DiagnosticFormat, OutputFormat};
|
use crate::args::{CompileCommand, DiagnosticFormat, OutputFormat};
|
||||||
|
@ -4,7 +4,7 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
use fontdb::{Database, Source};
|
use fontdb::{Database, Source};
|
||||||
use typst::diag::StrResult;
|
use typst::diag::StrResult;
|
||||||
use typst::font::{Font, FontBook, FontInfo, FontVariant};
|
use typst::text::{Font, FontBook, FontInfo, FontVariant};
|
||||||
|
|
||||||
use crate::args::FontsCommand;
|
use crate::args::FontsCommand;
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ impl FontSearcher {
|
|||||||
#[cfg(feature = "embed-fonts")]
|
#[cfg(feature = "embed-fonts")]
|
||||||
fn add_embedded(&mut self) {
|
fn add_embedded(&mut self) {
|
||||||
let mut process = |bytes: &'static [u8]| {
|
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() {
|
for (i, font) in Font::iter(buffer).enumerate() {
|
||||||
self.book.push(font.info().clone());
|
self.book.push(font.info().clone());
|
||||||
self.fonts.push(FontSlot {
|
self.fonts.push(FontSlot {
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
use comemo::Track;
|
use comemo::Track;
|
||||||
|
use ecow::{eco_format, EcoString};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use typst::diag::{bail, StrResult};
|
use typst::diag::{bail, StrResult};
|
||||||
use typst::eval::{eval_string, EvalMode, Tracer};
|
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::World;
|
||||||
use typst_library::prelude::*;
|
|
||||||
|
|
||||||
use crate::args::{QueryCommand, SerializationFormat};
|
use crate::args::{QueryCommand, SerializationFormat};
|
||||||
use crate::compile::print_diagnostics;
|
use crate::compile::print_diagnostics;
|
||||||
@ -95,7 +98,7 @@ fn format(elements: Vec<Content>, command: &QueryCommand) -> StrResult<String> {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if command.one {
|
if command.one {
|
||||||
let Some(value) = mapped.get(0) else {
|
let Some(value) = mapped.first() else {
|
||||||
bail!("no such field found for element");
|
bail!("no such field found for element");
|
||||||
};
|
};
|
||||||
serialize(value, command.format)
|
serialize(value, command.format)
|
||||||
|
@ -6,8 +6,9 @@ use inferno::flamegraph::Options;
|
|||||||
use tracing::metadata::LevelFilter;
|
use tracing::metadata::LevelFilter;
|
||||||
use tracing_error::ErrorLayer;
|
use tracing_error::ErrorLayer;
|
||||||
use tracing_flame::{FlameLayer, FlushGuard};
|
use tracing_flame::{FlameLayer, FlushGuard};
|
||||||
use tracing_subscriber::fmt;
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
use tracing_subscriber::prelude::*;
|
use tracing_subscriber::util::SubscriberInitExt;
|
||||||
|
use tracing_subscriber::{fmt, Layer};
|
||||||
|
|
||||||
use crate::args::{CliArguments, Command};
|
use crate::args::{CliArguments, Command};
|
||||||
|
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
use std::env;
|
|
||||||
use std::fs;
|
|
||||||
use std::io::{Cursor, Read, Write};
|
use std::io::{Cursor, Read, Write};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::{env, fs};
|
||||||
|
|
||||||
|
use ecow::eco_format;
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
use typst::diag::{bail, StrResult};
|
use typst::diag::{bail, StrResult};
|
||||||
use typst::eval::eco_format;
|
|
||||||
use xz2::bufread::XzDecoder;
|
use xz2::bufread::XzDecoder;
|
||||||
use zip::ZipArchive;
|
use zip::ZipArchive;
|
||||||
|
|
||||||
|
@ -3,11 +3,11 @@ use std::io::{self, IsTerminal, Write};
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use codespan_reporting::term::{self, termcolor};
|
use codespan_reporting::term::{self, termcolor};
|
||||||
|
use ecow::eco_format;
|
||||||
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
|
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
|
||||||
use same_file::is_same_file;
|
use same_file::is_same_file;
|
||||||
use termcolor::WriteColor;
|
use termcolor::WriteColor;
|
||||||
use typst::diag::StrResult;
|
use typst::diag::StrResult;
|
||||||
use typst::eval::eco_format;
|
|
||||||
|
|
||||||
use crate::args::CompileCommand;
|
use crate::args::CompileCommand;
|
||||||
use crate::color_stream;
|
use crate::color_stream;
|
||||||
|
@ -5,13 +5,14 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use chrono::{DateTime, Datelike, Local};
|
use chrono::{DateTime, Datelike, Local};
|
||||||
use comemo::Prehashed;
|
use comemo::Prehashed;
|
||||||
|
use ecow::eco_format;
|
||||||
use typst::diag::{FileError, FileResult, StrResult};
|
use typst::diag::{FileError, FileResult, StrResult};
|
||||||
use typst::doc::Frame;
|
use typst::foundations::{Bytes, Datetime};
|
||||||
use typst::eval::{eco_format, Bytes, Datetime, Library};
|
use typst::layout::Frame;
|
||||||
use typst::font::{Font, FontBook};
|
|
||||||
use typst::syntax::{FileId, Source, VirtualPath};
|
use typst::syntax::{FileId, Source, VirtualPath};
|
||||||
|
use typst::text::{Font, FontBook};
|
||||||
use typst::util::hash128;
|
use typst::util::hash128;
|
||||||
use typst::World;
|
use typst::{Library, World};
|
||||||
|
|
||||||
use crate::args::SharedArgs;
|
use crate::args::SharedArgs;
|
||||||
use crate::fonts::{FontSearcher, FontSlot};
|
use crate::fonts::{FontSearcher, FontSlot};
|
||||||
@ -75,7 +76,7 @@ impl SystemWorld {
|
|||||||
input,
|
input,
|
||||||
root,
|
root,
|
||||||
main: FileId::new(None, main_path),
|
main: FileId::new(None, main_path),
|
||||||
library: Prehashed::new(typst_library::build()),
|
library: Prehashed::new(Library::build()),
|
||||||
book: Prehashed::new(searcher.book),
|
book: Prehashed::new(searcher.book),
|
||||||
fonts: searcher.fonts,
|
fonts: searcher.fonts,
|
||||||
slots: RefCell::default(),
|
slots: RefCell::default(),
|
||||||
|
@ -12,7 +12,6 @@ bench = false
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
typst = { workspace = true }
|
typst = { workspace = true }
|
||||||
typst-library = { workspace = true }
|
|
||||||
comemo = { workspace = true }
|
comemo = { workspace = true }
|
||||||
ecow = { workspace = true }
|
ecow = { workspace = true }
|
||||||
heck = { workspace = true }
|
heck = { workspace = true }
|
||||||
|
@ -4,7 +4,7 @@ use std::fmt::Write;
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::{Html, Resolver};
|
use crate::{Html, Resolver};
|
||||||
|
|
||||||
/// Build HTML detailing the contributors between two tags.
|
/// Build HTML detailing the contributors between two tags.
|
||||||
pub fn contributors(resolver: &dyn Resolver, from: &str, to: &str) -> Option<Html> {
|
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 serde::{Deserialize, Serialize};
|
||||||
use typed_arena::Arena;
|
use typed_arena::Arena;
|
||||||
use typst::diag::{FileResult, StrResult};
|
use typst::diag::{FileResult, StrResult};
|
||||||
use typst::eval::{Bytes, Datetime, Library, Tracer};
|
use typst::eval::Tracer;
|
||||||
use typst::font::{Font, FontBook};
|
use typst::foundations::{Bytes, Datetime};
|
||||||
use typst::geom::{Abs, Point, Size};
|
use typst::layout::{Abs, Point, Size};
|
||||||
use typst::syntax::{FileId, Source, VirtualPath};
|
use typst::syntax::{FileId, Source, VirtualPath};
|
||||||
use typst::World;
|
use typst::text::{Font, FontBook};
|
||||||
|
use typst::{Library, World};
|
||||||
use unscanny::Scanner;
|
use unscanny::Scanner;
|
||||||
use yaml_front_matter::YamlFrontMatter;
|
use yaml_front_matter::YamlFrontMatter;
|
||||||
|
|
||||||
@ -360,13 +361,13 @@ fn code_block(resolver: &dyn Resolver, lang: &str, text: &str) -> Html {
|
|||||||
buf.push_str("</pre>");
|
buf.push_str("</pre>");
|
||||||
return Html::new(buf);
|
return Html::new(buf);
|
||||||
} else if !matches!(lang, "example" | "typ" | "preview") {
|
} 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(
|
let buf = syntect::html::highlighted_html_for_string(
|
||||||
&display,
|
&display,
|
||||||
set,
|
set,
|
||||||
set.find_syntax_by_token(lang)
|
set.find_syntax_by_token(lang)
|
||||||
.unwrap_or_else(|| panic!("unsupported highlighting language: {lang}")),
|
.unwrap_or_else(|| panic!("unsupported highlighting language: {lang}")),
|
||||||
&typst_library::text::THEME,
|
&typst::text::RAW_THEME,
|
||||||
)
|
)
|
||||||
.expect("failed to highlight code");
|
.expect("failed to highlight code");
|
||||||
return Html::new(buf);
|
return Html::new(buf);
|
||||||
|
@ -5,9 +5,9 @@ mod html;
|
|||||||
mod link;
|
mod link;
|
||||||
mod model;
|
mod model;
|
||||||
|
|
||||||
pub use contribs::{contributors, Author, Commit};
|
pub use self::contribs::*;
|
||||||
pub use html::Html;
|
pub use self::html::*;
|
||||||
pub use model::*;
|
pub use self::model::*;
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
@ -20,30 +20,48 @@ use serde::de::DeserializeOwned;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_yaml as yaml;
|
use serde_yaml as yaml;
|
||||||
use typst::diag::{bail, StrResult};
|
use typst::diag::{bail, StrResult};
|
||||||
use typst::doc::Frame;
|
use typst::foundations::{
|
||||||
use typst::eval::{
|
CastInfo, Category, Func, Module, ParamInfo, Repr, Scope, Smart, Type, Value,
|
||||||
CastInfo, Func, Library, Module, ParamInfo, Repr, Scope, Smart, Type, Value,
|
FOUNDATIONS,
|
||||||
};
|
};
|
||||||
use typst::font::{Font, FontBook};
|
use typst::introspection::INTROSPECTION;
|
||||||
use typst::geom::Abs;
|
use typst::layout::{Abs, Frame, Margin, PageElem, LAYOUT};
|
||||||
use typst_library::layout::{Margin, PageElem};
|
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 DOCS_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../../docs");
|
||||||
static FILE_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../../assets/files");
|
static FILE_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../../assets/files");
|
||||||
static FONT_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../../assets/fonts");
|
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(|| {
|
||||||
static GROUPS: Lazy<Vec<GroupData>> = Lazy::new(|| yaml("reference/groups.yml"));
|
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(|| {
|
static LIBRARY: Lazy<Prehashed<Library>> = Lazy::new(|| {
|
||||||
let mut lib = typst_library::build();
|
let mut lib = Library::build();
|
||||||
lib.styles
|
lib.styles
|
||||||
.set(PageElem::set_width(Smart::Custom(Abs::pt(240.0).into())));
|
.set(PageElem::set_width(Smart::Custom(Abs::pt(240.0).into())));
|
||||||
lib.styles.set(PageElem::set_height(Smart::Auto));
|
lib.styles.set(PageElem::set_height(Smart::Auto));
|
||||||
lib.styles.set(PageElem::set_margin(Margin::splat(Some(Smart::Custom(
|
lib.styles.set(PageElem::set_margin(Margin::splat(Some(Smart::Custom(
|
||||||
Abs::pt(15.0).into(),
|
Abs::pt(15.0).into(),
|
||||||
)))));
|
)))));
|
||||||
typst::eval::set_lang_items(lib.items.clone());
|
|
||||||
Prehashed::new(lib)
|
Prehashed::new(lib)
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -128,14 +146,15 @@ fn reference_pages(resolver: &dyn Resolver) -> PageModel {
|
|||||||
.with_part("Language"),
|
.with_part("Language"),
|
||||||
markdown_page(resolver, "/docs/reference/", "reference/styling.md"),
|
markdown_page(resolver, "/docs/reference/", "reference/styling.md"),
|
||||||
markdown_page(resolver, "/docs/reference/", "reference/scripting.md"),
|
markdown_page(resolver, "/docs/reference/", "reference/scripting.md"),
|
||||||
category_page(resolver, "foundations").with_part("Library"),
|
category_page(resolver, FOUNDATIONS).with_part("Library"),
|
||||||
category_page(resolver, "text"),
|
category_page(resolver, MODEL),
|
||||||
category_page(resolver, "math"),
|
category_page(resolver, TEXT),
|
||||||
category_page(resolver, "layout"),
|
category_page(resolver, MATH),
|
||||||
category_page(resolver, "visualize"),
|
category_page(resolver, SYMBOLS),
|
||||||
category_page(resolver, "meta"),
|
category_page(resolver, LAYOUT),
|
||||||
category_page(resolver, "symbols"),
|
category_page(resolver, VISUALIZE),
|
||||||
category_page(resolver, "data-loading"),
|
category_page(resolver, INTROSPECTION),
|
||||||
|
category_page(resolver, DATA_LOADING),
|
||||||
];
|
];
|
||||||
page
|
page
|
||||||
}
|
}
|
||||||
@ -152,50 +171,73 @@ fn guide_pages(resolver: &dyn Resolver) -> PageModel {
|
|||||||
|
|
||||||
/// Build the packages section.
|
/// Build the packages section.
|
||||||
fn packages_page(resolver: &dyn Resolver) -> PageModel {
|
fn packages_page(resolver: &dyn Resolver) -> PageModel {
|
||||||
|
let md = DOCS_DIR
|
||||||
|
.get_file("reference/packages.md")
|
||||||
|
.unwrap()
|
||||||
|
.contents_utf8()
|
||||||
|
.unwrap();
|
||||||
PageModel {
|
PageModel {
|
||||||
route: "/docs/packages/".into(),
|
route: "/docs/packages/".into(),
|
||||||
title: "Packages".into(),
|
title: "Packages".into(),
|
||||||
description: "Packages for Typst.".into(),
|
description: "Packages for Typst.".into(),
|
||||||
part: None,
|
part: None,
|
||||||
outline: vec![],
|
outline: vec![],
|
||||||
body: BodyModel::Packages(Html::markdown(
|
body: BodyModel::Packages(Html::markdown(resolver, md, Some(1))),
|
||||||
resolver,
|
|
||||||
category_details("packages"),
|
|
||||||
Some(1),
|
|
||||||
)),
|
|
||||||
children: vec![],
|
children: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a page for a category.
|
/// Create a page for a category.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn category_page(resolver: &dyn Resolver, category: &str) -> PageModel {
|
fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel {
|
||||||
let route = eco_format!("/docs/reference/{category}/");
|
let route = eco_format!("/docs/reference/{}/", category.name());
|
||||||
let mut children = vec![];
|
let mut children = vec![];
|
||||||
let mut items = vec![];
|
let mut items = vec![];
|
||||||
|
let mut shorthands = None;
|
||||||
|
let mut markup = vec![];
|
||||||
|
let mut math = vec![];
|
||||||
|
|
||||||
let (module, path): (&Module, &[&str]) = match category {
|
let (module, path): (&Module, &[&str]) = if category == MATH {
|
||||||
"math" => (&LIBRARY.math, &["math"]),
|
(&LIBRARY.math, &["math"])
|
||||||
_ => (&LIBRARY.global, &[]),
|
} else {
|
||||||
|
(&LIBRARY.global, &[])
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add groups.
|
// Add groups.
|
||||||
for mut group in GROUPS.iter().filter(|g| g.category == category).cloned() {
|
for group in GROUPS.iter().filter(|g| g.category == category.name()).cloned() {
|
||||||
let mut focus = module;
|
if matches!(group.name.as_str(), "sym" | "emoji") {
|
||||||
if matches!(group.name.as_str(), "calc" | "sys") {
|
let subpage = symbols_page(resolver, &route, &group);
|
||||||
focus = get_module(focus, &group.name).unwrap();
|
let BodyModel::Symbols(model) = &subpage.body else { continue };
|
||||||
group.functions = focus
|
let list = &model.list;
|
||||||
.scope()
|
markup.extend(
|
||||||
.iter()
|
list.iter()
|
||||||
.filter(|(_, v)| matches!(v, Value::Func(_)))
|
.filter(|symbol| symbol.markup_shorthand.is_some())
|
||||||
.map(|(k, _)| k.clone())
|
.cloned(),
|
||||||
.collect();
|
);
|
||||||
|
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);
|
children.push(child);
|
||||||
items.push(item);
|
items.push(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add symbol pages. These are ordered manually.
|
||||||
|
if category == SYMBOLS {
|
||||||
|
shorthands = Some(ShorthandsModel { markup, math });
|
||||||
|
}
|
||||||
|
|
||||||
// Add functions.
|
// Add functions.
|
||||||
let scope = module.scope();
|
let scope = module.scope();
|
||||||
for (name, value) in scope.iter() {
|
for (name, value) in scope.iter() {
|
||||||
@ -203,9 +245,9 @@ fn category_page(resolver: &dyn Resolver, category: &str) -> PageModel {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if category == "math" {
|
if category == MATH {
|
||||||
// Skip grouped functions.
|
// 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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,54 +284,35 @@ fn category_page(resolver: &dyn Resolver, category: &str) -> PageModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
children.sort_by_cached_key(|child| child.title.clone());
|
if category != SYMBOLS {
|
||||||
items.sort_by_cached_key(|item| item.name.clone());
|
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 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let name: EcoString = category.to_title_case().into();
|
let name = category.title();
|
||||||
|
let details = Html::markdown(resolver, category.docs(), Some(1));
|
||||||
let details = Html::markdown(resolver, category_details(category), Some(1));
|
|
||||||
let mut outline = vec![OutlineItem::from_name("Summary")];
|
let mut outline = vec![OutlineItem::from_name("Summary")];
|
||||||
outline.extend(details.outline());
|
outline.extend(details.outline());
|
||||||
outline.push(OutlineItem::from_name("Definitions"));
|
outline.push(OutlineItem::from_name("Definitions"));
|
||||||
|
if shorthands.is_some() {
|
||||||
|
outline.push(OutlineItem::from_name("Shorthands"));
|
||||||
|
}
|
||||||
|
|
||||||
PageModel {
|
PageModel {
|
||||||
route,
|
route,
|
||||||
title: name.clone(),
|
title: name.into(),
|
||||||
description: eco_format!(
|
description: eco_format!(
|
||||||
"Documentation for functions related to {name} in Typst."
|
"Documentation for functions related to {name} in Typst."
|
||||||
),
|
),
|
||||||
part: None,
|
part: None,
|
||||||
outline,
|
outline,
|
||||||
body: BodyModel::Category(CategoryModel { name, details, items, shorthands }),
|
body: BodyModel::Category(CategoryModel {
|
||||||
|
name: category.name(),
|
||||||
|
title: category.title(),
|
||||||
|
details,
|
||||||
|
items,
|
||||||
|
shorthands,
|
||||||
|
}),
|
||||||
children,
|
children,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -498,18 +521,17 @@ fn group_page(
|
|||||||
resolver: &dyn Resolver,
|
resolver: &dyn Resolver,
|
||||||
parent: &str,
|
parent: &str,
|
||||||
group: &GroupData,
|
group: &GroupData,
|
||||||
scope: &Scope,
|
|
||||||
) -> (PageModel, CategoryItem) {
|
) -> (PageModel, CategoryItem) {
|
||||||
let mut functions = vec![];
|
let mut functions = vec![];
|
||||||
let mut outline = vec![OutlineItem::from_name("Summary")];
|
let mut outline = vec![OutlineItem::from_name("Summary")];
|
||||||
|
|
||||||
let path: Vec<_> = group.path.iter().map(|s| s.as_str()).collect();
|
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());
|
outline.extend(details.outline());
|
||||||
|
|
||||||
let mut outline_items = vec![];
|
let mut outline_items = vec![];
|
||||||
for name in &group.functions {
|
for name in &group.filter {
|
||||||
let value = scope.get(name).unwrap();
|
let value = group.module().scope().get(name).unwrap();
|
||||||
let Value::Func(func) = value else { panic!("not a function") };
|
let Value::Func(func) = value else { panic!("not a function") };
|
||||||
let func = func_model(resolver, func, &path, true);
|
let func = func_model(resolver, func, &path, true);
|
||||||
let id_base = urlify(&eco_format!("functions-{}", func.name));
|
let id_base = urlify(&eco_format!("functions-{}", func.name));
|
||||||
@ -530,13 +552,13 @@ fn group_page(
|
|||||||
|
|
||||||
let model = PageModel {
|
let model = PageModel {
|
||||||
route: eco_format!("{parent}{}", group.name),
|
route: eco_format!("{parent}{}", group.name),
|
||||||
title: group.display.clone(),
|
title: group.title.clone(),
|
||||||
description: eco_format!("Documentation for the {} functions.", group.name),
|
description: eco_format!("Documentation for the {} functions.", group.name),
|
||||||
part: None,
|
part: None,
|
||||||
outline,
|
outline,
|
||||||
body: BodyModel::Group(GroupModel {
|
body: BodyModel::Group(GroupModel {
|
||||||
name: group.name.clone(),
|
name: group.name.clone(),
|
||||||
title: group.display.clone(),
|
title: group.title.clone(),
|
||||||
details,
|
details,
|
||||||
functions,
|
functions,
|
||||||
}),
|
}),
|
||||||
@ -546,7 +568,7 @@ fn group_page(
|
|||||||
let item = CategoryItem {
|
let item = CategoryItem {
|
||||||
name: group.name.clone(),
|
name: group.name.clone(),
|
||||||
route: model.route.clone(),
|
route: model.route.clone(),
|
||||||
oneliner: oneliner(&group.description).into(),
|
oneliner: oneliner(&group.details).into(),
|
||||||
code: false,
|
code: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -601,19 +623,12 @@ fn type_outline(model: &TypeModel) -> Vec<OutlineItem> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a page for symbols.
|
/// Create a page for symbols.
|
||||||
fn symbols_page(resolver: &dyn Resolver, parent: &str, name: &str) -> PageModel {
|
fn symbols_page(resolver: &dyn Resolver, parent: &str, group: &GroupData) -> PageModel {
|
||||||
let module = get_module(&LIBRARY.global, name).unwrap();
|
let model = symbols_model(resolver, group);
|
||||||
let title = match name {
|
|
||||||
"sym" => "General",
|
|
||||||
"emoji" => "Emoji",
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let model = symbols_model(resolver, name, title, module.scope());
|
|
||||||
PageModel {
|
PageModel {
|
||||||
route: eco_format!("{parent}{name}/"),
|
route: eco_format!("{parent}{}/", group.name),
|
||||||
title: title.into(),
|
title: group.title.clone(),
|
||||||
description: eco_format!("Documentation for the `{name}` module."),
|
description: eco_format!("Documentation for the `{}` module.", group.name),
|
||||||
part: None,
|
part: None,
|
||||||
outline: vec![],
|
outline: vec![],
|
||||||
body: BodyModel::Symbols(model),
|
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.
|
/// Produce a symbol list's model.
|
||||||
fn symbols_model(
|
fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel {
|
||||||
resolver: &dyn Resolver,
|
|
||||||
name: &str,
|
|
||||||
title: &'static str,
|
|
||||||
scope: &Scope,
|
|
||||||
) -> SymbolsModel {
|
|
||||||
let mut list = vec![];
|
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 Value::Symbol(symbol) = value else { continue };
|
||||||
let complete = |variant: &str| {
|
let complete = |variant: &str| {
|
||||||
if variant.is_empty() {
|
if variant.is_empty() {
|
||||||
@ -649,7 +659,7 @@ fn symbols_model(
|
|||||||
markup_shorthand: shorthand(typst::syntax::ast::Shorthand::MARKUP_LIST),
|
markup_shorthand: shorthand(typst::syntax::ast::Shorthand::MARKUP_LIST),
|
||||||
math_shorthand: shorthand(typst::syntax::ast::Shorthand::MATH_LIST),
|
math_shorthand: shorthand(typst::syntax::ast::Shorthand::MATH_LIST),
|
||||||
codepoint: c as u32,
|
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)
|
unicode_name: unicode_names2::name(c)
|
||||||
.map(|s| s.to_string().to_title_case().into()),
|
.map(|s| s.to_string().to_title_case().into()),
|
||||||
alternates: symbol
|
alternates: symbol
|
||||||
@ -662,8 +672,9 @@ fn symbols_model(
|
|||||||
}
|
}
|
||||||
|
|
||||||
SymbolsModel {
|
SymbolsModel {
|
||||||
name: title,
|
name: group.name.clone(),
|
||||||
details: Html::markdown(resolver, category_details(name), Some(1)),
|
title: group.title.clone(),
|
||||||
|
details: Html::markdown(resolver, &group.details, Some(1)),
|
||||||
list,
|
list,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -684,15 +695,6 @@ fn yaml<T: DeserializeOwned>(path: &str) -> T {
|
|||||||
yaml::from_slice(file.contents()).unwrap()
|
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.
|
/// Turn a title into an URL fragment.
|
||||||
pub fn urlify(title: &str) -> EcoString {
|
pub fn urlify(title: &str) -> EcoString {
|
||||||
title
|
title
|
||||||
@ -752,13 +754,23 @@ const TYPE_ORDER: &[&str] = &[
|
|||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
struct GroupData {
|
struct GroupData {
|
||||||
name: EcoString,
|
name: EcoString,
|
||||||
|
title: EcoString,
|
||||||
category: EcoString,
|
category: EcoString,
|
||||||
display: EcoString,
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
path: Vec<EcoString>,
|
path: Vec<EcoString>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
functions: Vec<EcoString>,
|
filter: Vec<EcoString>,
|
||||||
description: 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)]
|
#[cfg(test)]
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use typst::diag::{bail, StrResult};
|
use typst::diag::{bail, StrResult};
|
||||||
use typst::eval::Func;
|
use typst::foundations::Func;
|
||||||
|
|
||||||
use crate::{get_module, GROUPS, LIBRARY};
|
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> {
|
fn resolve_definition(head: &str) -> StrResult<String> {
|
||||||
let mut parts = head.trim_start_matches('$').split('.').peekable();
|
let mut parts = head.trim_start_matches('$').split('.').peekable();
|
||||||
let mut focus = &LIBRARY.global;
|
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()) {
|
while let Some(m) = parts.peek().and_then(|&name| get_module(focus, name).ok()) {
|
||||||
focus = m;
|
focus = m;
|
||||||
parts.next();
|
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 name = parts.next().ok_or("link is missing first part")?;
|
||||||
let value = focus.field(name)?;
|
let value = focus.field(name)?;
|
||||||
let Some(category) = focus.scope().get_category(name) else {
|
|
||||||
bail!("{name} has no category");
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle grouped functions.
|
// Handle grouped functions.
|
||||||
if let Some(group) = GROUPS
|
if let Some(group) = GROUPS.iter().find(|group| {
|
||||||
.iter()
|
group.category == category.name() && group.filter.iter().any(|func| func == name)
|
||||||
.filter(|_| category == "math")
|
}) {
|
||||||
.find(|group| group.functions.iter().any(|func| func == name))
|
let mut route = format!(
|
||||||
{
|
"/docs/reference/{}/{}/#functions-{}",
|
||||||
let mut route =
|
group.category, group.name, name
|
||||||
format!("/docs/reference/math/{}/#functions-{}", group.name, name);
|
);
|
||||||
if let Some(param) = parts.next() {
|
if let Some(param) = parts.next() {
|
||||||
route.push('-');
|
route.push('-');
|
||||||
route.push_str(param);
|
route.push_str(param);
|
||||||
@ -81,7 +87,7 @@ fn resolve_definition(head: &str) -> StrResult<String> {
|
|||||||
return Ok(route);
|
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 let Some(next) = parts.next() {
|
||||||
if value.field(next).is_ok() {
|
if value.field(next).is_ok() {
|
||||||
route.push_str("#definitions-");
|
route.push_str("#definitions-");
|
||||||
|
@ -62,7 +62,8 @@ pub enum BodyModel {
|
|||||||
/// Details about a category.
|
/// Details about a category.
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct CategoryModel {
|
pub struct CategoryModel {
|
||||||
pub name: EcoString,
|
pub name: &'static str,
|
||||||
|
pub title: &'static str,
|
||||||
pub details: Html,
|
pub details: Html,
|
||||||
pub items: Vec<CategoryItem>,
|
pub items: Vec<CategoryItem>,
|
||||||
pub shorthands: Option<ShorthandsModel>,
|
pub shorthands: Option<ShorthandsModel>,
|
||||||
@ -144,7 +145,8 @@ pub struct TypeModel {
|
|||||||
/// A collection of symbols.
|
/// A collection of symbols.
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct SymbolsModel {
|
pub struct SymbolsModel {
|
||||||
pub name: &'static str,
|
pub name: EcoString,
|
||||||
|
pub title: EcoString,
|
||||||
pub details: Html,
|
pub details: Html,
|
||||||
pub list: Vec<SymbolModel>,
|
pub list: Vec<SymbolModel>,
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
use comemo::Track;
|
use comemo::Track;
|
||||||
use ecow::{eco_vec, EcoString, EcoVec};
|
use ecow::{eco_vec, EcoString, EcoVec};
|
||||||
use typst::doc::Frame;
|
use typst::diag::DelayedErrors;
|
||||||
use typst::eval::{Route, Scopes, Tracer, Value, Vm};
|
use typst::eval::{Route, Tracer, Vm};
|
||||||
use typst::model::{DelayedErrors, Introspector, Label, Locator, Vt};
|
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::syntax::{ast, LinkedNode, Span, SyntaxKind};
|
||||||
use typst::World;
|
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
|
/// - All labels and descriptions for them, if available
|
||||||
/// - A split offset: All labels before this offset belong to nodes, all after
|
/// - A split offset: All labels before this offset belong to nodes, all after
|
||||||
/// belong to a bibliography.
|
/// belong to a bibliography.
|
||||||
pub fn analyze_labels(
|
pub fn analyze_labels(frames: &[Frame]) -> (Vec<(Label, Option<EcoString>)>, usize) {
|
||||||
world: &dyn World,
|
|
||||||
frames: &[Frame],
|
|
||||||
) -> (Vec<(Label, Option<EcoString>)>, usize) {
|
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
let introspector = Introspector::new(frames);
|
let introspector = Introspector::new(frames);
|
||||||
let items = &world.library().items;
|
|
||||||
|
|
||||||
// Labels in the document.
|
// Labels in the document.
|
||||||
for elem in introspector.all() {
|
for elem in introspector.all() {
|
||||||
@ -102,7 +101,7 @@ pub fn analyze_labels(
|
|||||||
let split = output.len();
|
let split = output.len();
|
||||||
|
|
||||||
// Bibliography keys.
|
// 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));
|
output.push((Label::new(&key), detail));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,21 +4,21 @@ use std::collections::{BTreeSet, HashSet};
|
|||||||
use ecow::{eco_format, EcoString};
|
use ecow::{eco_format, EcoString};
|
||||||
use if_chain::if_chain;
|
use if_chain::if_chain;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use typst::doc::Frame;
|
use typst::foundations::{
|
||||||
use typst::eval::{
|
fields_on, format_str, mutable_methods_on, repr, AutoValue, CastInfo, Func, Label,
|
||||||
format_str, repr, AutoValue, CastInfo, Func, Library, NoneValue, Repr, Scope, Type,
|
NoneValue, Repr, Scope, Type, Value,
|
||||||
Value,
|
|
||||||
};
|
};
|
||||||
use typst::geom::Color;
|
use typst::layout::Frame;
|
||||||
use typst::model::Label;
|
|
||||||
use typst::syntax::{
|
use typst::syntax::{
|
||||||
ast, is_id_continue, is_id_start, is_ident, LinkedNode, Source, SyntaxKind,
|
ast, is_id_continue, is_id_start, is_ident, LinkedNode, Source, SyntaxKind,
|
||||||
};
|
};
|
||||||
|
use typst::text::RawElem;
|
||||||
|
use typst::visualize::Color;
|
||||||
use typst::World;
|
use typst::World;
|
||||||
use unscanny::Scanner;
|
use unscanny::Scanner;
|
||||||
|
|
||||||
use crate::analyze::analyze_labels;
|
use crate::analyze::{analyze_expr, analyze_import, analyze_labels};
|
||||||
use crate::{analyze_expr, analyze_import, plain_docs_sentence, summarize_font_family};
|
use crate::{plain_docs_sentence, summarize_font_family};
|
||||||
|
|
||||||
/// Autocomplete a cursor position in a source file.
|
/// 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 {
|
ctx.completions.push(Completion {
|
||||||
kind: CompletionKind::Func,
|
kind: CompletionKind::Func,
|
||||||
label: method.into(),
|
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:
|
// Complete the field name along with its value. Notes:
|
||||||
// 1. No parentheses since function fields cannot currently be called
|
// 1. No parentheses since function fields cannot currently be called
|
||||||
// with method syntax;
|
// with method syntax;
|
||||||
@ -967,7 +967,6 @@ fn code_completions(ctx: &mut CompletionContext, hash: bool) {
|
|||||||
struct CompletionContext<'a> {
|
struct CompletionContext<'a> {
|
||||||
world: &'a (dyn World + 'a),
|
world: &'a (dyn World + 'a),
|
||||||
frames: &'a [Frame],
|
frames: &'a [Frame],
|
||||||
library: &'a Library,
|
|
||||||
global: &'a Scope,
|
global: &'a Scope,
|
||||||
math: &'a Scope,
|
math: &'a Scope,
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
@ -996,7 +995,6 @@ impl<'a> CompletionContext<'a> {
|
|||||||
Some(Self {
|
Some(Self {
|
||||||
world,
|
world,
|
||||||
frames,
|
frames,
|
||||||
library,
|
|
||||||
global: library.global.scope(),
|
global: library.global.scope(),
|
||||||
math: library.math.scope(),
|
math: library.math.scope(),
|
||||||
text,
|
text,
|
||||||
@ -1074,7 +1072,7 @@ impl<'a> CompletionContext<'a> {
|
|||||||
|
|
||||||
/// Add completions for raw block tags.
|
/// Add completions for raw block tags.
|
||||||
fn raw_completions(&mut self) {
|
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();
|
let lower = name.to_lowercase();
|
||||||
if !tags.contains(&lower.as_str()) {
|
if !tags.contains(&lower.as_str()) {
|
||||||
tags.push(lower.as_str());
|
tags.push(lower.as_str());
|
||||||
@ -1096,7 +1094,7 @@ impl<'a> CompletionContext<'a> {
|
|||||||
|
|
||||||
/// Add completions for labels and references.
|
/// Add completions for labels and references.
|
||||||
fn label_completions(&mut self) {
|
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 head = &self.text[..self.from];
|
||||||
let at = head.ends_with('@');
|
let at = head.ends_with('@');
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
use typst::doc::{Destination, Frame, FrameItem, Meta, Position};
|
use typst::introspection::{Introspector, Meta};
|
||||||
use typst::geom::{Geometry, Point, Size};
|
use typst::layout::{Frame, FrameItem, Point, Position, Size};
|
||||||
use typst::model::Introspector;
|
use typst::model::Destination;
|
||||||
use typst::syntax::{FileId, LinkedNode, Source, Span, SyntaxKind};
|
use typst::syntax::{FileId, LinkedNode, Source, Span, SyntaxKind};
|
||||||
|
use typst::visualize::Geometry;
|
||||||
use typst::World;
|
use typst::World;
|
||||||
|
|
||||||
/// Where to [jump](jump_from_click) to.
|
/// Where to [jump](jump_from_click) to.
|
||||||
|
@ -13,9 +13,7 @@ pub use self::tooltip::{tooltip, Tooltip};
|
|||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
use ecow::{eco_format, EcoString};
|
use ecow::{eco_format, EcoString};
|
||||||
use typst::font::{FontInfo, FontStyle};
|
use typst::text::{FontInfo, FontStyle};
|
||||||
|
|
||||||
use self::analyze::*;
|
|
||||||
|
|
||||||
/// Extract the first sentence of plain text of a piece of documentation.
|
/// 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 ecow::{eco_format, EcoString};
|
||||||
use if_chain::if_chain;
|
use if_chain::if_chain;
|
||||||
use typst::doc::Frame;
|
use typst::eval::{CapturesVisitor, Tracer};
|
||||||
use typst::eval::{repr, CapturesVisitor, CastInfo, Repr, Tracer, Value};
|
use typst::foundations::{repr, CastInfo, Repr, Value};
|
||||||
use typst::geom::{round_2, Length, Numeric};
|
use typst::layout::{Frame, Length};
|
||||||
use typst::syntax::{ast, LinkedNode, Source, SyntaxKind};
|
use typst::syntax::{ast, LinkedNode, Source, SyntaxKind};
|
||||||
|
use typst::util::{round_2, Numeric};
|
||||||
use typst::World;
|
use typst::World;
|
||||||
|
|
||||||
use crate::analyze::analyze_labels;
|
use crate::analyze::{analyze_expr, analyze_labels};
|
||||||
use crate::{analyze_expr, plain_docs_sentence, summarize_font_family};
|
use crate::{plain_docs_sentence, summarize_font_family};
|
||||||
|
|
||||||
/// Describe the item under the cursor.
|
/// Describe the item under the cursor.
|
||||||
pub fn tooltip(
|
pub fn tooltip(
|
||||||
@ -25,7 +26,7 @@ pub fn tooltip(
|
|||||||
|
|
||||||
named_param_tooltip(world, &leaf)
|
named_param_tooltip(world, &leaf)
|
||||||
.or_else(|| font_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(|| expr_tooltip(world, &leaf))
|
||||||
.or_else(|| closure_tooltip(&leaf))
|
.or_else(|| closure_tooltip(&leaf))
|
||||||
}
|
}
|
||||||
@ -144,18 +145,14 @@ fn length_tooltip(length: Length) -> Option<Tooltip> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Tooltip for a hovered reference or label.
|
/// Tooltip for a hovered reference or label.
|
||||||
fn label_tooltip(
|
fn label_tooltip(frames: &[Frame], leaf: &LinkedNode) -> Option<Tooltip> {
|
||||||
world: &dyn World,
|
|
||||||
frames: &[Frame],
|
|
||||||
leaf: &LinkedNode,
|
|
||||||
) -> Option<Tooltip> {
|
|
||||||
let target = match leaf.kind() {
|
let target = match leaf.kind() {
|
||||||
SyntaxKind::RefMarker => leaf.text().trim_start_matches('@'),
|
SyntaxKind::RefMarker => leaf.text().trim_start_matches('@'),
|
||||||
SyntaxKind::Label => leaf.text().trim_start_matches('<').trim_end_matches('>'),
|
SyntaxKind::Label => leaf.text().trim_start_matches('<').trim_end_matches('>'),
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
for (label, detail) in analyze_labels(world, frames).0 {
|
for (label, detail) in analyze_labels(frames).0 {
|
||||||
if label.as_str() == target {
|
if label.as_str() == target {
|
||||||
return Some(Tooltip::Text(detail?));
|
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.
|
/// Expand the `#[derive(Cast)]` macro.
|
||||||
pub fn derive_cast(item: DeriveInput) -> Result<TokenStream> {
|
pub fn derive_cast(item: DeriveInput) -> Result<TokenStream> {
|
||||||
@ -43,9 +50,9 @@ pub fn derive_cast(item: DeriveInput) -> Result<TokenStream> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
::typst::eval::cast! {
|
#foundations::cast! {
|
||||||
#ty,
|
#ty,
|
||||||
self => ::typst::eval::IntoValue::into_value(match self {
|
self => #foundations::IntoValue::into_value(match self {
|
||||||
#(#variants_to_strs),*
|
#(#variants_to_strs),*
|
||||||
}),
|
}),
|
||||||
#(#strs_to_variants),*
|
#(#strs_to_variants),*
|
||||||
@ -62,8 +69,6 @@ struct Variant {
|
|||||||
|
|
||||||
/// Expand the `cast!` macro.
|
/// Expand the `cast!` macro.
|
||||||
pub fn cast(stream: TokenStream) -> Result<TokenStream> {
|
pub fn cast(stream: TokenStream) -> Result<TokenStream> {
|
||||||
let eval = quote! { ::typst::eval };
|
|
||||||
|
|
||||||
let input: CastInput = syn::parse2(stream)?;
|
let input: CastInput = syn::parse2(stream)?;
|
||||||
let ty = &input.ty;
|
let ty = &input.ty;
|
||||||
let castable_body = create_castable_body(&input);
|
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(|| {
|
let reflect = (!input.from_value.is_empty() || input.dynamic).then(|| {
|
||||||
quote! {
|
quote! {
|
||||||
impl #eval::Reflect for #ty {
|
impl #foundations::Reflect for #ty {
|
||||||
fn input() -> #eval::CastInfo {
|
fn input() -> #foundations::CastInfo {
|
||||||
#input_body
|
#input_body
|
||||||
}
|
}
|
||||||
|
|
||||||
fn output() -> #eval::CastInfo {
|
fn output() -> #foundations::CastInfo {
|
||||||
#output_body
|
#output_body
|
||||||
}
|
}
|
||||||
|
|
||||||
fn castable(value: &#eval::Value) -> bool {
|
fn castable(value: &#foundations::Value) -> bool {
|
||||||
#castable_body
|
#castable_body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,8 +97,8 @@ pub fn cast(stream: TokenStream) -> Result<TokenStream> {
|
|||||||
|
|
||||||
let into_value = (input.into_value.is_some() || input.dynamic).then(|| {
|
let into_value = (input.into_value.is_some() || input.dynamic).then(|| {
|
||||||
quote! {
|
quote! {
|
||||||
impl #eval::IntoValue for #ty {
|
impl #foundations::IntoValue for #ty {
|
||||||
fn into_value(self) -> #eval::Value {
|
fn into_value(self) -> #foundations::Value {
|
||||||
#into_value_body
|
#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(|| {
|
let from_value = (!input.from_value.is_empty() || input.dynamic).then(|| {
|
||||||
quote! {
|
quote! {
|
||||||
impl #eval::FromValue for #ty {
|
impl #foundations::FromValue for #ty {
|
||||||
fn from_value(value: #eval::Value) -> ::typst::diag::StrResult<Self> {
|
fn from_value(value: #foundations::Value) -> ::typst::diag::StrResult<Self> {
|
||||||
#from_value_body
|
#from_value_body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -196,7 +201,7 @@ fn create_castable_body(input: &CastInput) -> TokenStream {
|
|||||||
}
|
}
|
||||||
Pattern::Ty(_, ty) => {
|
Pattern::Ty(_, ty) => {
|
||||||
casts.push(quote! {
|
casts.push(quote! {
|
||||||
if <#ty as ::typst::eval::Reflect>::castable(value) {
|
if <#ty as #foundations::Reflect>::castable(value) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -206,7 +211,7 @@ fn create_castable_body(input: &CastInput) -> TokenStream {
|
|||||||
|
|
||||||
let dynamic_check = input.dynamic.then(|| {
|
let dynamic_check = input.dynamic.then(|| {
|
||||||
quote! {
|
quote! {
|
||||||
if let ::typst::eval::Value::Dyn(dynamic) = &value {
|
if let #foundations::Value::Dyn(dynamic) = &value {
|
||||||
if dynamic.is::<Self>() {
|
if dynamic.is::<Self>() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -216,7 +221,7 @@ fn create_castable_body(input: &CastInput) -> TokenStream {
|
|||||||
|
|
||||||
let str_check = (!strings.is_empty()).then(|| {
|
let str_check = (!strings.is_empty()).then(|| {
|
||||||
quote! {
|
quote! {
|
||||||
if let ::typst::eval::Value::Str(string) = &value {
|
if let #foundations::Value::Str(string) = &value {
|
||||||
match string.as_str() {
|
match string.as_str() {
|
||||||
#(#strings,)*
|
#(#strings,)*
|
||||||
_ => {}
|
_ => {}
|
||||||
@ -241,21 +246,21 @@ fn create_input_body(input: &CastInput) -> TokenStream {
|
|||||||
infos.push(match &cast.pattern {
|
infos.push(match &cast.pattern {
|
||||||
Pattern::Str(lit) => {
|
Pattern::Str(lit) => {
|
||||||
quote! {
|
quote! {
|
||||||
::typst::eval::CastInfo::Value(
|
#foundations::CastInfo::Value(
|
||||||
::typst::eval::IntoValue::into_value(#lit),
|
#foundations::IntoValue::into_value(#lit),
|
||||||
#docs,
|
#docs,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Pattern::Ty(_, ty) => {
|
Pattern::Ty(_, ty) => {
|
||||||
quote! { <#ty as ::typst::eval::Reflect>::input() }
|
quote! { <#ty as #foundations::Reflect>::input() }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.dynamic {
|
if input.dynamic {
|
||||||
infos.push(quote! {
|
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 {
|
fn create_output_body(input: &CastInput) -> TokenStream {
|
||||||
if input.dynamic {
|
if input.dynamic {
|
||||||
quote! { ::typst::eval::CastInfo::Type(::typst::eval::Type::of::<Self>()) }
|
quote! { #foundations::CastInfo::Type(#foundations::Type::of::<Self>()) }
|
||||||
} else {
|
} else {
|
||||||
quote! { Self::input() }
|
quote! { Self::input() }
|
||||||
}
|
}
|
||||||
@ -276,7 +281,7 @@ fn create_into_value_body(input: &CastInput) -> TokenStream {
|
|||||||
if let Some(expr) = &input.into_value {
|
if let Some(expr) = &input.into_value {
|
||||||
quote! { #expr }
|
quote! { #expr }
|
||||||
} else {
|
} 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) => {
|
Pattern::Ty(binding, ty) => {
|
||||||
cast_checks.push(quote! {
|
cast_checks.push(quote! {
|
||||||
if <#ty as ::typst::eval::Reflect>::castable(&value) {
|
if <#ty as #foundations::Reflect>::castable(&value) {
|
||||||
let #binding = <#ty as ::typst::eval::FromValue>::from_value(value)?;
|
let #binding = <#ty as #foundations::FromValue>::from_value(value)?;
|
||||||
return Ok(#expr);
|
return Ok(#expr);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -303,7 +308,7 @@ fn create_from_value_body(input: &CastInput) -> TokenStream {
|
|||||||
|
|
||||||
let dynamic_check = input.dynamic.then(|| {
|
let dynamic_check = input.dynamic.then(|| {
|
||||||
quote! {
|
quote! {
|
||||||
if let ::typst::eval::Value::Dyn(dynamic) = &value {
|
if let #foundations::Value::Dyn(dynamic) = &value {
|
||||||
if let Some(concrete) = dynamic.downcast::<Self>() {
|
if let Some(concrete) = dynamic.downcast::<Self>() {
|
||||||
return Ok(concrete.clone());
|
return Ok(concrete.clone());
|
||||||
}
|
}
|
||||||
@ -313,7 +318,7 @@ fn create_from_value_body(input: &CastInput) -> TokenStream {
|
|||||||
|
|
||||||
let str_check = (!string_arms.is_empty()).then(|| {
|
let str_check = (!string_arms.is_empty()).then(|| {
|
||||||
quote! {
|
quote! {
|
||||||
if let ::typst::eval::Value::Str(string) = &value {
|
if let #foundations::Value::Str(string) = &value {
|
||||||
match string.as_str() {
|
match string.as_str() {
|
||||||
#(#string_arms,)*
|
#(#string_arms,)*
|
||||||
_ => {}
|
_ => {}
|
||||||
@ -326,6 +331,6 @@ fn create_from_value_body(input: &CastInput) -> TokenStream {
|
|||||||
#dynamic_check
|
#dynamic_check
|
||||||
#str_check
|
#str_check
|
||||||
#(#cast_checks)*
|
#(#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.
|
/// Expand the `#[elem]` macro.
|
||||||
pub fn elem(stream: TokenStream, body: syn::ItemStruct) -> Result<TokenStream> {
|
pub fn elem(stream: TokenStream, body: syn::ItemStruct) -> Result<TokenStream> {
|
||||||
@ -136,7 +147,6 @@ struct Field {
|
|||||||
synthesized: bool,
|
synthesized: bool,
|
||||||
borrowed: bool,
|
borrowed: bool,
|
||||||
ghost: bool,
|
ghost: bool,
|
||||||
forced_variant: Option<usize>,
|
|
||||||
parse: Option<BlockWithReturn>,
|
parse: Option<BlockWithReturn>,
|
||||||
default: Option<syn::Expr>,
|
default: Option<syn::Expr>,
|
||||||
}
|
}
|
||||||
@ -226,10 +236,6 @@ fn parse_field(field: &syn::Field) -> Result<Field> {
|
|||||||
docs: documentation(&attrs),
|
docs: documentation(&attrs),
|
||||||
internal: has_attr(&mut attrs, "internal"),
|
internal: has_attr(&mut attrs, "internal"),
|
||||||
external: has_attr(&mut attrs, "external"),
|
external: has_attr(&mut attrs, "external"),
|
||||||
forced_variant: parse_attr::<syn::LitInt>(&mut attrs, "variant")?
|
|
||||||
.flatten()
|
|
||||||
.map(|lit| lit.base10_parse())
|
|
||||||
.transpose()?,
|
|
||||||
positional,
|
positional,
|
||||||
required,
|
required,
|
||||||
variadic,
|
variadic,
|
||||||
@ -262,12 +268,12 @@ fn parse_field(field: &syn::Field) -> Result<Field> {
|
|||||||
|
|
||||||
if field.resolve {
|
if field.resolve {
|
||||||
let output = &field.output;
|
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 {
|
if field.fold {
|
||||||
let output = &field.output;
|
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)?;
|
validate_attrs(&attrs)?;
|
||||||
@ -317,8 +323,8 @@ fn create(element: &Elem) -> Result<TokenStream> {
|
|||||||
|
|
||||||
let label_and_location = element.unless_capability("Unlabellable", || {
|
let label_and_location = element.unless_capability("Unlabellable", || {
|
||||||
quote! {
|
quote! {
|
||||||
location: Option<::typst::model::Location>,
|
location: Option<::typst::introspection::Location>,
|
||||||
label: Option<::typst::model::Label>,
|
label: Option<#foundations::Label>,
|
||||||
prepared: bool,
|
prepared: bool,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -330,7 +336,7 @@ fn create(element: &Elem) -> Result<TokenStream> {
|
|||||||
#vis struct #ident {
|
#vis struct #ident {
|
||||||
span: ::typst::syntax::Span,
|
span: ::typst::syntax::Span,
|
||||||
#label_and_location
|
#label_and_location
|
||||||
guards: ::std::vec::Vec<::typst::model::Guard>,
|
guards: ::std::vec::Vec<#foundations::Guard>,
|
||||||
|
|
||||||
#(#fields,)*
|
#(#fields,)*
|
||||||
}
|
}
|
||||||
@ -353,9 +359,9 @@ fn create(element: &Elem) -> Result<TokenStream> {
|
|||||||
#partial_eq_impl
|
#partial_eq_impl
|
||||||
#repr_impl
|
#repr_impl
|
||||||
|
|
||||||
impl ::typst::eval::IntoValue for #ident {
|
impl #foundations::IntoValue for #ident {
|
||||||
fn into_value(self) -> ::typst::eval::Value {
|
fn into_value(self) -> #foundations::Value {
|
||||||
::typst::eval::Value::Content(::typst::model::Content::new(self))
|
#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.
|
/// Creates the element's enum for field identifiers.
|
||||||
fn create_fields_enum(element: &Elem) -> TokenStream {
|
fn create_fields_enum(element: &Elem) -> TokenStream {
|
||||||
let model = quote! { ::typst::model };
|
|
||||||
let Elem { ident, enum_ident, .. } = element;
|
let Elem { ident, enum_ident, .. } = element;
|
||||||
|
|
||||||
let mut fields = element.real_fields().collect::<Vec<_>>();
|
let fields = element.real_fields().collect::<Vec<_>>();
|
||||||
fields.sort_by_key(|field| field.forced_variant.unwrap_or(usize::MAX));
|
|
||||||
|
|
||||||
let field_names = fields.iter().map(|Field { name, .. }| name).collect::<Vec<_>>();
|
let field_names = fields.iter().map(|Field { name, .. }| name).collect::<Vec<_>>();
|
||||||
let field_consts = fields
|
let field_consts = fields
|
||||||
.iter()
|
.iter()
|
||||||
@ -399,20 +402,14 @@ fn create_fields_enum(element: &Elem) -> TokenStream {
|
|||||||
.map(|Field { enum_ident, .. }| enum_ident)
|
.map(|Field { enum_ident, .. }| enum_ident)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let definitions =
|
let definitions = fields.iter().map(|Field { enum_ident, .. }| {
|
||||||
fields.iter().map(|Field { forced_variant, enum_ident, .. }| {
|
quote! { #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 }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
// To hide the private type
|
// To hide the private type
|
||||||
const _: () = {
|
const _: () = {
|
||||||
impl #model::ElementFields for #ident {
|
impl #foundations::ElementFields for #ident {
|
||||||
type Fields = #enum_ident;
|
type Fields = #enum_ident;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -578,16 +575,15 @@ fn create_push_field_method(field: &Field) -> TokenStream {
|
|||||||
|
|
||||||
/// Create a setter method for a field.
|
/// Create a setter method for a field.
|
||||||
fn create_set_field_method(element: &Elem, field: &Field) -> TokenStream {
|
fn create_set_field_method(element: &Elem, field: &Field) -> TokenStream {
|
||||||
let model = quote! { ::typst::model };
|
|
||||||
let elem = &element.ident;
|
let elem = &element.ident;
|
||||||
let Field { vis, ident, set_ident, enum_ident, ty, name, .. } = field;
|
let Field { vis, ident, set_ident, enum_ident, ty, name, .. } = field;
|
||||||
let doc = format!("Create a style property for the `{}` field.", name);
|
let doc = format!("Create a style property for the `{}` field.", name);
|
||||||
quote! {
|
quote! {
|
||||||
#[doc = #doc]
|
#[doc = #doc]
|
||||||
#vis fn #set_ident(#ident: #ty) -> ::typst::model::Style {
|
#vis fn #set_ident(#ident: #ty) -> #foundations::Style {
|
||||||
::typst::model::Style::Property(::typst::model::Property::new(
|
#foundations::Style::Property(#foundations::Property::new(
|
||||||
<Self as ::typst::model::NativeElement>::elem(),
|
<Self as #foundations::NativeElement>::elem(),
|
||||||
<#elem as #model::ElementFields>::Fields::#enum_ident as u8,
|
<#elem as #foundations::ElementFields>::Fields::#enum_ident as u8,
|
||||||
#ident,
|
#ident,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -608,7 +604,7 @@ fn create_field_in_method(element: &Elem, field: &Field) -> TokenStream {
|
|||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#[doc = #doc]
|
#[doc = #doc]
|
||||||
#vis fn #ident_in(styles: ::typst::model::StyleChain) -> #output {
|
#vis fn #ident_in(styles: #foundations::StyleChain) -> #output {
|
||||||
#access
|
#access
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -646,7 +642,7 @@ fn create_field_method(element: &Elem, field: &Field) -> TokenStream {
|
|||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#[doc = #docs]
|
#[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
|
#access
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -655,7 +651,7 @@ fn create_field_method(element: &Elem, field: &Field) -> TokenStream {
|
|||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#[doc = #docs]
|
#[doc = #docs]
|
||||||
#vis fn #ident(&self, styles: ::typst::model::StyleChain) -> #output {
|
#vis fn #ident(&self, styles: #foundations::StyleChain) -> #output {
|
||||||
#access
|
#access
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -668,7 +664,6 @@ fn create_style_chain_access(
|
|||||||
field: &Field,
|
field: &Field,
|
||||||
inherent: TokenStream,
|
inherent: TokenStream,
|
||||||
) -> TokenStream {
|
) -> TokenStream {
|
||||||
let model = quote! { ::typst::model };
|
|
||||||
let elem = &element.ident;
|
let elem = &element.ident;
|
||||||
|
|
||||||
let Field { ty, default, enum_ident, .. } = field;
|
let Field { ty, default, enum_ident, .. } = field;
|
||||||
@ -693,8 +688,8 @@ fn create_style_chain_access(
|
|||||||
quote! {
|
quote! {
|
||||||
#init
|
#init
|
||||||
styles.#getter::<#ty>(
|
styles.#getter::<#ty>(
|
||||||
<Self as ::typst::model::NativeElement>::elem(),
|
<Self as #foundations::NativeElement>::elem(),
|
||||||
<#elem as #model::ElementFields>::Fields::#enum_ident as u8,
|
<#elem as #foundations::ElementFields>::Fields::#enum_ident as u8,
|
||||||
#inherent,
|
#inherent,
|
||||||
#default,
|
#default,
|
||||||
)
|
)
|
||||||
@ -703,9 +698,6 @@ fn create_style_chain_access(
|
|||||||
|
|
||||||
/// Creates the element's `Pack` implementation.
|
/// Creates the element's `Pack` implementation.
|
||||||
fn create_native_elem_impl(element: &Elem) -> TokenStream {
|
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 Elem { name, ident, title, scope, keywords, docs, .. } = element;
|
||||||
|
|
||||||
let vtable_func = create_vtable_func(element);
|
let vtable_func = create_vtable_func(element);
|
||||||
@ -716,9 +708,9 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream {
|
|||||||
.map(create_param_info);
|
.map(create_param_info);
|
||||||
|
|
||||||
let scope = if *scope {
|
let scope = if *scope {
|
||||||
quote! { <#ident as #eval::NativeScope>::scope() }
|
quote! { <#ident as #foundations::NativeScope>::scope() }
|
||||||
} else {
|
} else {
|
||||||
quote! { #eval::Scope::new() }
|
quote! { #foundations::Scope::new() }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fields that can be accessed using the `field` method.
|
// Fields that can be accessed using the `field` method.
|
||||||
@ -729,37 +721,39 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream {
|
|||||||
|
|
||||||
if field.ghost {
|
if field.ghost {
|
||||||
quote! {
|
quote! {
|
||||||
<#elem as #model::ElementFields>::Fields::#name => None,
|
<#elem as #foundations::ElementFields>::Fields::#name => None,
|
||||||
}
|
}
|
||||||
} else if field.inherent() {
|
} else if field.inherent() {
|
||||||
quote! {
|
quote! {
|
||||||
<#elem as #model::ElementFields>::Fields::#name => Some(
|
<#elem as #foundations::ElementFields>::Fields::#name => Some(
|
||||||
::typst::eval::IntoValue::into_value(self.#field_ident.clone())
|
#foundations::IntoValue::into_value(self.#field_ident.clone())
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quote! {
|
quote! {
|
||||||
<#elem as #model::ElementFields>::Fields::#name => {
|
<#elem as #foundations::ElementFields>::Fields::#name => {
|
||||||
self.#field_ident.clone().map(::typst::eval::IntoValue::into_value)
|
self.#field_ident.clone().map(#foundations::IntoValue::into_value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fields that can be set using the `set_field` method.
|
// Fields that can be set using the `set_field` method.
|
||||||
let field_set_matches = element.visible_fields()
|
let field_set_matches = element
|
||||||
.filter(|field| field.settable() && !field.synthesized && !field.ghost).map(|field| {
|
.visible_fields()
|
||||||
let elem = &element.ident;
|
.filter(|field| field.settable() && !field.synthesized && !field.ghost)
|
||||||
let name = &field.enum_ident;
|
.map(|field| {
|
||||||
let field_ident = &field.ident;
|
let elem = &element.ident;
|
||||||
|
let name = &field.enum_ident;
|
||||||
|
let field_ident = &field.ident;
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
<#elem as #model::ElementFields>::Fields::#name => {
|
<#elem as #foundations::ElementFields>::Fields::#name => {
|
||||||
self.#field_ident = Some(::typst::eval::FromValue::from_value(value)?);
|
self.#field_ident = Some(#foundations::FromValue::from_value(value)?);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// Fields that are inherent.
|
// Fields that are inherent.
|
||||||
let field_inherent_matches = element
|
let field_inherent_matches = element
|
||||||
@ -771,8 +765,8 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream {
|
|||||||
let field_ident = &field.ident;
|
let field_ident = &field.ident;
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
<#elem as #model::ElementFields>::Fields::#name => {
|
<#elem as #foundations::ElementFields>::Fields::#name => {
|
||||||
self.#field_ident = ::typst::eval::FromValue::from_value(value)?;
|
self.#field_ident = #foundations::FromValue::from_value(value)?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -790,13 +784,13 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream {
|
|||||||
// Internal fields create an error that they are unknown.
|
// Internal fields create an error that they are unknown.
|
||||||
let unknown_field = format!("unknown field `{field_name}` on `{name}`");
|
let unknown_field = format!("unknown field `{field_name}` on `{name}`");
|
||||||
quote! {
|
quote! {
|
||||||
<#elem as #model::ElementFields>::Fields::#ident => ::typst::diag::bail!(#unknown_field),
|
<#elem as #foundations::ElementFields>::Fields::#ident => ::typst::diag::bail!(#unknown_field),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fields that cannot be set create an error that they are not settable.
|
// Fields that cannot be set create an error that they are not settable.
|
||||||
let not_settable = format!("cannot set `{field_name}` on `{name}`");
|
let not_settable = format!("cannot set `{field_name}` on `{name}`");
|
||||||
quote! {
|
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_ident = &field.ident;
|
||||||
|
|
||||||
let field_call = if name.len() > 15 {
|
let field_call = if name.len() > 15 {
|
||||||
quote! { EcoString::from(#name).into() }
|
quote! { ::ecow::EcoString::from(#name).into() }
|
||||||
} else {
|
} else {
|
||||||
quote! { EcoString::inline(#name).into() }
|
quote! { ::ecow::EcoString::inline(#name).into() }
|
||||||
};
|
};
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
fields.insert(
|
fields.insert(
|
||||||
#field_call,
|
#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_ident = &field.ident;
|
||||||
|
|
||||||
let field_call = if name.len() > 15 {
|
let field_call = if name.len() > 15 {
|
||||||
quote! { EcoString::from(#name).into() }
|
quote! { ::ecow::EcoString::from(#name).into() }
|
||||||
} else {
|
} else {
|
||||||
quote! { EcoString::inline(#name).into() }
|
quote! { ::ecow::EcoString::inline(#name).into() }
|
||||||
};
|
};
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
if let Some(value) = &self.#field_ident {
|
if let Some(value) = &self.#field_ident {
|
||||||
fields.insert(
|
fields.insert(
|
||||||
#field_call,
|
#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
|
let label_field = element
|
||||||
.unless_capability("Unlabellable", || {
|
.unless_capability("Unlabellable", || {
|
||||||
quote! {
|
quote! {
|
||||||
self.label().map(::typst::eval::Value::Label)
|
self.label().map(#foundations::Value::Label)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| quote! { None });
|
.unwrap_or_else(|| quote! { None });
|
||||||
@ -905,51 +899,51 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream {
|
|||||||
let local_name = element
|
let local_name = element
|
||||||
.if_capability(
|
.if_capability(
|
||||||
"LocalName",
|
"LocalName",
|
||||||
|| quote! { Some(<#ident as ::typst::model::LocalName>::local_name) },
|
|| quote! { Some(<#ident as ::typst::text::LocalName>::local_name) },
|
||||||
)
|
)
|
||||||
.unwrap_or_else(|| quote! { None });
|
.unwrap_or_else(|| quote! { None });
|
||||||
|
|
||||||
let unknown_field = format!("unknown field {{}} on {}", name);
|
let unknown_field = format!("unknown field {{}} on {}", name);
|
||||||
let label_error = format!("cannot set label on {}", name);
|
let label_error = format!("cannot set label on {}", name);
|
||||||
let data = quote! {
|
let data = quote! {
|
||||||
#model::NativeElementData {
|
#foundations::NativeElementData {
|
||||||
name: #name,
|
name: #name,
|
||||||
title: #title,
|
title: #title,
|
||||||
docs: #docs,
|
docs: #docs,
|
||||||
keywords: &[#(#keywords),*],
|
keywords: &[#(#keywords),*],
|
||||||
construct: <#ident as #model::Construct>::construct,
|
construct: <#ident as #foundations::Construct>::construct,
|
||||||
set: <#ident as #model::Set>::set,
|
set: <#ident as #foundations::Set>::set,
|
||||||
vtable: #vtable_func,
|
vtable: #vtable_func,
|
||||||
field_id: |name|
|
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),
|
>::from_str(name).ok().map(|id| id as u8),
|
||||||
field_name: |id|
|
field_name: |id|
|
||||||
<
|
<
|
||||||
<#ident as #model::ElementFields>::Fields as ::std::convert::TryFrom<u8>
|
<#ident as #foundations::ElementFields>::Fields as ::std::convert::TryFrom<u8>
|
||||||
>::try_from(id).ok().map(<#ident as #model::ElementFields>::Fields::to_str),
|
>::try_from(id).ok().map(<#ident as #foundations::ElementFields>::Fields::to_str),
|
||||||
local_name: #local_name,
|
local_name: #local_name,
|
||||||
scope: #eval::Lazy::new(|| #scope),
|
scope: #foundations::Lazy::new(|| #scope),
|
||||||
params: #eval::Lazy::new(|| ::std::vec![#(#params),*])
|
params: #foundations::Lazy::new(|| ::std::vec![#(#params),*])
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
impl #model::NativeElement for #ident {
|
impl #foundations::NativeElement for #ident {
|
||||||
fn data() -> &'static #model::NativeElementData {
|
fn data() -> &'static #foundations::NativeElementData {
|
||||||
static DATA: #model::NativeElementData = #data;
|
static DATA: #foundations::NativeElementData = #data;
|
||||||
&DATA
|
&DATA
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dyn_elem(&self) -> #model::Element {
|
fn dyn_elem(&self) -> #foundations::Element {
|
||||||
#model::Element::of::<Self>()
|
#foundations::Element::of::<Self>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dyn_hash(&self, mut hasher: &mut dyn ::std::hash::Hasher) {
|
fn dyn_hash(&self, mut hasher: &mut dyn ::std::hash::Hasher) {
|
||||||
<Self as ::std::hash::Hash>::hash(self, &mut 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>() {
|
if let Some(other) = other.to::<Self>() {
|
||||||
<Self as ::std::cmp::PartialEq>::eq(self, other)
|
<Self as ::std::cmp::PartialEq>::eq(self, other)
|
||||||
} else {
|
} 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))
|
::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
|
#label
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_label(&mut self, label: #model::Label) {
|
fn set_label(&mut self, label: #foundations::Label) {
|
||||||
#set_label
|
#set_label
|
||||||
}
|
}
|
||||||
|
|
||||||
fn location(&self) -> Option<#model::Location> {
|
fn location(&self) -> Option<::typst::introspection::Location> {
|
||||||
#location
|
#location
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_location(&mut self, location: #model::Location) {
|
fn set_location(&mut self, location: ::typst::introspection::Location) {
|
||||||
#set_location
|
#set_location
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_guard(&mut self, guard: #model::Guard) {
|
fn push_guard(&mut self, guard: #foundations::Guard) {
|
||||||
self.guards.push(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)
|
self.guards.contains(&guard)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1023,30 +1017,30 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream {
|
|||||||
#prepared
|
#prepared
|
||||||
}
|
}
|
||||||
|
|
||||||
fn field(&self, id: u8) -> Option<::typst::eval::Value> {
|
fn field(&self, id: u8) -> Option<#foundations::Value> {
|
||||||
let id = <#ident as #model::ElementFields>::Fields::try_from(id).ok()?;
|
let id = <#ident as #foundations::ElementFields>::Fields::try_from(id).ok()?;
|
||||||
match id {
|
match id {
|
||||||
<#ident as #model::ElementFields>::Fields::Label => #label_field,
|
<#ident as #foundations::ElementFields>::Fields::Label => #label_field,
|
||||||
#(#field_matches)*
|
#(#field_matches)*
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fields(&self) -> Dict {
|
fn fields(&self) -> #foundations::Dict {
|
||||||
let mut fields = Dict::new();
|
let mut fields = #foundations::Dict::new();
|
||||||
#(#field_dict)*
|
#(#field_dict)*
|
||||||
#(#field_opt_dict)*
|
#(#field_opt_dict)*
|
||||||
fields
|
fields
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_field(&mut self, id: u8, value: Value) -> ::typst::diag::StrResult<()> {
|
fn set_field(&mut self, id: u8, value: #foundations::Value) -> ::typst::diag::StrResult<()> {
|
||||||
let id = <#ident as #model::ElementFields>::Fields::try_from(id)
|
let id = <#ident as #foundations::ElementFields>::Fields::try_from(id)
|
||||||
.map_err(|_| ::ecow::eco_format!(#unknown_field, id))?;
|
.map_err(|_| ::ecow::eco_format!(#unknown_field, id))?;
|
||||||
match id {
|
match id {
|
||||||
#(#field_set_matches)*
|
#(#field_set_matches)*
|
||||||
#(#field_inherent_matches)*
|
#(#field_inherent_matches)*
|
||||||
#(#field_not_set_matches)*
|
#(#field_not_set_matches)*
|
||||||
<#ident as #model::ElementFields>::Fields::Label => {
|
<#ident as #foundations::ElementFields>::Fields::Label => {
|
||||||
::typst::diag::bail!(#label_error);
|
::typst::diag::bail!(#label_error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1087,18 +1081,18 @@ fn create_construct_impl(element: &Elem) -> TokenStream {
|
|||||||
.map(|field| &field.ident);
|
.map(|field| &field.ident);
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
impl ::typst::model::Construct for #ident {
|
impl #foundations::Construct for #ident {
|
||||||
fn construct(
|
fn construct(
|
||||||
vm: &mut ::typst::eval::Vm,
|
vm: &mut ::typst::eval::Vm,
|
||||||
args: &mut ::typst::eval::Args,
|
args: &mut #foundations::Args,
|
||||||
) -> ::typst::diag::SourceResult<::typst::model::Content> {
|
) -> ::typst::diag::SourceResult<#foundations::Content> {
|
||||||
#(#pre)*
|
#(#pre)*
|
||||||
|
|
||||||
let mut element = Self::new(#(#defaults),*);
|
let mut element = Self::new(#(#defaults),*);
|
||||||
|
|
||||||
#(#handlers)*
|
#(#handlers)*
|
||||||
|
|
||||||
Ok(::typst::model::Content::new(element))
|
Ok(#foundations::Content::new(element))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1119,12 +1113,12 @@ fn create_set_impl(element: &Elem) -> TokenStream {
|
|||||||
});
|
});
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
impl ::typst::model::Set for #ident {
|
impl #foundations::Set for #ident {
|
||||||
fn set(
|
fn set(
|
||||||
vm: &mut Vm,
|
vm: &mut ::typst::eval::Vm,
|
||||||
args: &mut ::typst::eval::Args,
|
args: &mut #foundations::Args,
|
||||||
) -> ::typst::diag::SourceResult<::typst::model::Styles> {
|
) -> ::typst::diag::SourceResult<#foundations::Styles> {
|
||||||
let mut styles = ::typst::model::Styles::new();
|
let mut styles = #foundations::Styles::new();
|
||||||
#(#handlers)*
|
#(#handlers)*
|
||||||
Ok(styles)
|
Ok(styles)
|
||||||
}
|
}
|
||||||
@ -1135,7 +1129,7 @@ fn create_set_impl(element: &Elem) -> TokenStream {
|
|||||||
/// Creates the element's `Locatable` implementation.
|
/// Creates the element's `Locatable` implementation.
|
||||||
fn create_locatable_impl(element: &Elem) -> TokenStream {
|
fn create_locatable_impl(element: &Elem) -> TokenStream {
|
||||||
let ident = &element.ident;
|
let ident = &element.ident;
|
||||||
quote! { impl ::typst::model::Locatable for #ident {} }
|
quote! { impl ::typst::introspection::Locatable for #ident {} }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates the element's `PartialEq` implementation.
|
/// Creates the element's `PartialEq` implementation.
|
||||||
@ -1159,12 +1153,12 @@ fn create_repr_impl(element: &Elem) -> TokenStream {
|
|||||||
let ident = &element.ident;
|
let ident = &element.ident;
|
||||||
let repr_format = format!("{}{{}}", element.name);
|
let repr_format = format!("{}{{}}", element.name);
|
||||||
quote! {
|
quote! {
|
||||||
impl ::typst::eval::Repr for #ident {
|
impl #foundations::Repr for #ident {
|
||||||
fn repr(&self) -> ::ecow::EcoString {
|
fn repr(&self) -> ::ecow::EcoString {
|
||||||
let fields = self.fields().into_iter()
|
let fields = #foundations::NativeElement::fields(self).into_iter()
|
||||||
.map(|(name, value)| eco_format!("{}: {}", name, value.repr()))
|
.map(|(name, value)| ::ecow::eco_format!("{}: {}", name, value.repr()))
|
||||||
.collect::<Vec<_>>();
|
.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>() {
|
if id == ::std::any::TypeId::of::<dyn #capability>() {
|
||||||
let vtable = unsafe {
|
let vtable = unsafe {
|
||||||
let dangling = ::std::ptr::NonNull::<#ident>::dangling().as_ptr() as *const dyn #capability;
|
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);
|
return Some(vtable);
|
||||||
}
|
}
|
||||||
@ -1224,20 +1218,20 @@ fn create_param_info(field: &Field) -> TokenStream {
|
|||||||
quote! {
|
quote! {
|
||||||
|| {
|
|| {
|
||||||
let typed: #default_ty = #default;
|
let typed: #default_ty = #default;
|
||||||
::typst::eval::IntoValue::into_value(typed)
|
#foundations::IntoValue::into_value(typed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
let ty = if *variadic {
|
let ty = if *variadic {
|
||||||
quote! { <#ty as ::typst::eval::Container>::Inner }
|
quote! { <#ty as #foundations::Container>::Inner }
|
||||||
} else {
|
} else {
|
||||||
quote! { #ty }
|
quote! { #ty }
|
||||||
};
|
};
|
||||||
quote! {
|
quote! {
|
||||||
::typst::eval::ParamInfo {
|
#foundations::ParamInfo {
|
||||||
name: #name,
|
name: #name,
|
||||||
docs: #docs,
|
docs: #docs,
|
||||||
input: <#ty as ::typst::eval::Reflect>::input(),
|
input: <#ty as #foundations::Reflect>::input(),
|
||||||
default: #default,
|
default: #default,
|
||||||
positional: #positional,
|
positional: #positional,
|
||||||
named: #named,
|
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.
|
/// Expand the `#[func]` macro.
|
||||||
pub fn func(stream: TokenStream, item: &syn::ItemFn) -> Result<TokenStream> {
|
pub fn func(stream: TokenStream, item: &syn::ItemFn) -> Result<TokenStream> {
|
||||||
@ -191,8 +201,6 @@ fn parse_param(
|
|||||||
|
|
||||||
/// Produce the function's definition.
|
/// Produce the function's definition.
|
||||||
fn create(func: &Func, item: &syn::ItemFn) -> TokenStream {
|
fn create(func: &Func, item: &syn::ItemFn) -> TokenStream {
|
||||||
let eval = quote! { ::typst::eval };
|
|
||||||
|
|
||||||
let Func { docs, vis, ident, .. } = func;
|
let Func { docs, vis, ident, .. } = func;
|
||||||
let item = rewrite_fn_item(item);
|
let item = rewrite_fn_item(item);
|
||||||
let ty = create_func_ty(func);
|
let ty = create_func_ty(func);
|
||||||
@ -200,9 +208,9 @@ fn create(func: &Func, item: &syn::ItemFn) -> TokenStream {
|
|||||||
|
|
||||||
let creator = if ty.is_some() {
|
let creator = if ty.is_some() {
|
||||||
quote! {
|
quote! {
|
||||||
impl #eval::NativeFunc for #ident {
|
impl #foundations::NativeFunc for #ident {
|
||||||
fn data() -> &'static #eval::NativeFuncData {
|
fn data() -> &'static #foundations::NativeFuncData {
|
||||||
static DATA: #eval::NativeFuncData = #data;
|
static DATA: #foundations::NativeFuncData = #data;
|
||||||
&DATA
|
&DATA
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -211,8 +219,8 @@ fn create(func: &Func, item: &syn::ItemFn) -> TokenStream {
|
|||||||
let ident_data = quote::format_ident!("{ident}_data");
|
let ident_data = quote::format_ident!("{ident}_data");
|
||||||
quote! {
|
quote! {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#vis fn #ident_data() -> &'static #eval::NativeFuncData {
|
#vis fn #ident_data() -> &'static #foundations::NativeFuncData {
|
||||||
static DATA: #eval::NativeFuncData = #data;
|
static DATA: #foundations::NativeFuncData = #data;
|
||||||
&DATA
|
&DATA
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -231,8 +239,6 @@ fn create(func: &Func, item: &syn::ItemFn) -> TokenStream {
|
|||||||
|
|
||||||
/// Create native function data for the function.
|
/// Create native function data for the function.
|
||||||
fn create_func_data(func: &Func) -> TokenStream {
|
fn create_func_data(func: &Func) -> TokenStream {
|
||||||
let eval = quote! { ::typst::eval };
|
|
||||||
|
|
||||||
let Func {
|
let Func {
|
||||||
ident,
|
ident,
|
||||||
name,
|
name,
|
||||||
@ -247,30 +253,30 @@ fn create_func_data(func: &Func) -> TokenStream {
|
|||||||
} = func;
|
} = func;
|
||||||
|
|
||||||
let scope = if *scope {
|
let scope = if *scope {
|
||||||
quote! { <#ident as #eval::NativeScope>::scope() }
|
quote! { <#ident as #foundations::NativeScope>::scope() }
|
||||||
} else {
|
} else {
|
||||||
quote! { #eval::Scope::new() }
|
quote! { #foundations::Scope::new() }
|
||||||
};
|
};
|
||||||
|
|
||||||
let closure = create_wrapper_closure(func);
|
let closure = create_wrapper_closure(func);
|
||||||
let params = func.special.self_.iter().chain(&func.params).map(create_param_info);
|
let params = func.special.self_.iter().chain(&func.params).map(create_param_info);
|
||||||
|
|
||||||
let name = if *constructor {
|
let name = if *constructor {
|
||||||
quote! { <#parent as #eval::NativeType>::NAME }
|
quote! { <#parent as #foundations::NativeType>::NAME }
|
||||||
} else {
|
} else {
|
||||||
quote! { #name }
|
quote! { #name }
|
||||||
};
|
};
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#eval::NativeFuncData {
|
#foundations::NativeFuncData {
|
||||||
function: #closure,
|
function: #closure,
|
||||||
name: #name,
|
name: #name,
|
||||||
title: #title,
|
title: #title,
|
||||||
docs: #docs,
|
docs: #docs,
|
||||||
keywords: &[#(#keywords),*],
|
keywords: &[#(#keywords),*],
|
||||||
scope: #eval::Lazy::new(|| #scope),
|
scope: #foundations::Lazy::new(|| #scope),
|
||||||
params: #eval::Lazy::new(|| ::std::vec![#(#params),*]),
|
params: #foundations::Lazy::new(|| ::std::vec![#(#params),*]),
|
||||||
returns: #eval::Lazy::new(|| <#returns as #eval::Reflect>::output()),
|
returns: #foundations::Lazy::new(|| <#returns as #foundations::Reflect>::output()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -335,7 +341,7 @@ fn create_wrapper_closure(func: &Func) -> TokenStream {
|
|||||||
#handlers
|
#handlers
|
||||||
#finish
|
#finish
|
||||||
let output = #call;
|
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 positional = !named;
|
||||||
let required = !named && default.is_none();
|
let required = !named && default.is_none();
|
||||||
let ty = if *variadic || (*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 {
|
} else {
|
||||||
quote! { #ty }
|
quote! { #ty }
|
||||||
};
|
};
|
||||||
@ -354,15 +360,15 @@ fn create_param_info(param: &Param) -> TokenStream {
|
|||||||
quote! {
|
quote! {
|
||||||
|| {
|
|| {
|
||||||
let typed: #ty = #default;
|
let typed: #ty = #default;
|
||||||
::typst::eval::IntoValue::into_value(typed)
|
#foundations::IntoValue::into_value(typed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
quote! {
|
quote! {
|
||||||
::typst::eval::ParamInfo {
|
#foundations::ParamInfo {
|
||||||
name: #name,
|
name: #name,
|
||||||
docs: #docs,
|
docs: #docs,
|
||||||
input: <#ty as ::typst::eval::Reflect>::input(),
|
input: <#ty as #foundations::Reflect>::input(),
|
||||||
default: #default,
|
default: #default,
|
||||||
positional: #positional,
|
positional: #positional,
|
||||||
named: #named,
|
named: #named,
|
||||||
|
@ -5,22 +5,15 @@ extern crate proc_macro;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod util;
|
mod util;
|
||||||
mod cast;
|
mod cast;
|
||||||
|
mod category;
|
||||||
mod elem;
|
mod elem;
|
||||||
mod func;
|
mod func;
|
||||||
mod scope;
|
mod scope;
|
||||||
mod symbols;
|
mod symbols;
|
||||||
mod ty;
|
mod ty;
|
||||||
|
|
||||||
use heck::*;
|
|
||||||
use proc_macro::TokenStream as BoundaryStream;
|
use proc_macro::TokenStream as BoundaryStream;
|
||||||
use proc_macro2::TokenStream;
|
use syn::DeriveInput;
|
||||||
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::*;
|
|
||||||
|
|
||||||
/// Makes a native Rust function usable as a Typst function.
|
/// 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
|
/// - `#[synthesized]`: The field cannot be specified in a constructor or set
|
||||||
/// rule. Instead, it is added to an element before its show rule runs
|
/// rule. Instead, it is added to an element before its show rule runs
|
||||||
/// through the `Synthesize` trait.
|
/// 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,
|
/// - `#[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
|
/// 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.
|
/// 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()
|
.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.
|
/// Implements `Reflect`, `FromValue`, and `IntoValue` for a type.
|
||||||
///
|
///
|
||||||
/// - `Reflect` makes Typst's runtime aware of the type's characteristics.
|
/// - `Reflect` makes Typst's runtime aware of the type's characteristics.
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
use heck::ToKebabCase;
|
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.
|
/// Expand the `#[scope]` macro.
|
||||||
pub fn scope(_: TokenStream, item: syn::Item) -> Result<TokenStream> {
|
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");
|
bail!(item, "expected module or impl item");
|
||||||
};
|
};
|
||||||
|
|
||||||
let eval = quote! { ::typst::eval };
|
|
||||||
let self_ty = &item.self_ty;
|
let self_ty = &item.self_ty;
|
||||||
|
|
||||||
let mut definitions = vec![];
|
let mut definitions = vec![];
|
||||||
@ -43,13 +45,13 @@ pub fn scope(_: TokenStream, item: syn::Item) -> Result<TokenStream> {
|
|||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
#base
|
#base
|
||||||
|
|
||||||
impl #eval::NativeScope for #self_ty {
|
impl #foundations::NativeScope for #self_ty {
|
||||||
fn constructor() -> ::std::option::Option<&'static #eval::NativeFuncData> {
|
fn constructor() -> ::std::option::Option<&'static #foundations::NativeFuncData> {
|
||||||
#constructor
|
#constructor
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scope() -> #eval::Scope {
|
fn scope() -> #foundations::Scope {
|
||||||
let mut scope = #eval::Scope::deduplicating();
|
let mut scope = #foundations::Scope::deduplicating();
|
||||||
#(#definitions;)*
|
#(#definitions;)*
|
||||||
scope
|
scope
|
||||||
}
|
}
|
||||||
@ -92,7 +94,7 @@ fn handle_fn(self_ty: &syn::Type, item: &mut syn::ImplItemFn) -> Result<FnKind>
|
|||||||
}
|
}
|
||||||
syn::Meta::List(list) => {
|
syn::Meta::List(list) => {
|
||||||
let tokens = &list.tokens;
|
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 };
|
list.tokens = quote! { #tokens, parent = #self_ty };
|
||||||
if meta.constructor {
|
if meta.constructor {
|
||||||
return Ok(FnKind::Constructor(quote! { Some(#self_ty::#ident_data()) }));
|
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);
|
let ident_data = quote::format_ident!("{}_data", sig.ident);
|
||||||
sigs.push(quote! { #sig; });
|
sigs.push(quote! { #sig; });
|
||||||
sigs.push(quote! {
|
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.
|
/// Expand the `symbols!` macro.
|
||||||
pub fn symbols(stream: TokenStream) -> Result<TokenStream> {
|
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 pairs = list.iter().map(|symbol| {
|
||||||
let name = symbol.name.to_string();
|
let name = symbol.name.to_string();
|
||||||
let kind = match &symbol.kind {
|
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) => {
|
Kind::Multiple(variants) => {
|
||||||
let variants = variants.iter().map(|variant| {
|
let variants = variants.iter().map(|variant| {
|
||||||
let name = &variant.name;
|
let name = &variant.name;
|
||||||
@ -15,7 +20,7 @@ pub fn symbols(stream: TokenStream) -> Result<TokenStream> {
|
|||||||
quote! { (#name, #c) }
|
quote! { (#name, #c) }
|
||||||
});
|
});
|
||||||
quote! {
|
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.
|
/// Expand the `#[ty]` macro.
|
||||||
pub fn ty(stream: TokenStream, item: syn::Item) -> Result<TokenStream> {
|
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.
|
/// Produce the output of the macro.
|
||||||
fn create(ty: &Type, item: Option<&syn::Item>) -> TokenStream {
|
fn create(ty: &Type, item: Option<&syn::Item>) -> TokenStream {
|
||||||
let eval = quote! { ::typst::eval };
|
|
||||||
|
|
||||||
let Type {
|
let Type {
|
||||||
ident, name, long, title, docs, keywords, scope, ..
|
ident, name, long, title, docs, keywords, scope, ..
|
||||||
} = ty;
|
} = ty;
|
||||||
|
|
||||||
let constructor = if *scope {
|
let constructor = if *scope {
|
||||||
quote! { <#ident as #eval::NativeScope>::constructor() }
|
quote! { <#ident as #foundations::NativeScope>::constructor() }
|
||||||
} else {
|
} else {
|
||||||
quote! { None }
|
quote! { None }
|
||||||
};
|
};
|
||||||
|
|
||||||
let scope = if *scope {
|
let scope = if *scope {
|
||||||
quote! { <#ident as #eval::NativeScope>::scope() }
|
quote! { <#ident as #foundations::NativeScope>::scope() }
|
||||||
} else {
|
} else {
|
||||||
quote! { #eval::Scope::new() }
|
quote! { #foundations::Scope::new() }
|
||||||
};
|
};
|
||||||
|
|
||||||
let data = quote! {
|
let data = quote! {
|
||||||
#eval::NativeTypeData {
|
#foundations::NativeTypeData {
|
||||||
name: #name,
|
name: #name,
|
||||||
long_name: #long,
|
long_name: #long,
|
||||||
title: #title,
|
title: #title,
|
||||||
docs: #docs,
|
docs: #docs,
|
||||||
keywords: &[#(#keywords),*],
|
keywords: &[#(#keywords),*],
|
||||||
constructor: #eval::Lazy::new(|| #constructor),
|
constructor: #foundations::Lazy::new(|| #constructor),
|
||||||
scope: #eval::Lazy::new(|| #scope),
|
scope: #foundations::Lazy::new(|| #scope),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#item
|
#item
|
||||||
|
|
||||||
impl #eval::NativeType for #ident {
|
impl #foundations::NativeType for #ident {
|
||||||
const NAME: &'static str = #name;
|
const NAME: &'static str = #name;
|
||||||
|
|
||||||
fn data() -> &'static #eval::NativeTypeData {
|
fn data() -> &'static #foundations::NativeTypeData {
|
||||||
static DATA: #eval::NativeTypeData = #data;
|
static DATA: #foundations::NativeTypeData = #data;
|
||||||
&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::token::Token;
|
||||||
use syn::Attribute;
|
use syn::{Attribute, Ident, Result, Token};
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
/// Return an error at the given item.
|
/// Return an error at the given item.
|
||||||
macro_rules! bail {
|
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:
|
/// For parsing attributes of the form:
|
||||||
/// #[attr(
|
/// #[attr(
|
||||||
/// statement;
|
/// 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.
|
/// Parse a bare `type Name;` item.
|
||||||
pub struct BareType {
|
pub struct BareType {
|
||||||
pub attrs: Vec<Attribute>,
|
pub attrs: Vec<Attribute>,
|
||||||
@ -239,7 +241,7 @@ pub struct BareType {
|
|||||||
|
|
||||||
impl Parse for BareType {
|
impl Parse for BareType {
|
||||||
fn parse(input: ParseStream) -> Result<Self> {
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
Ok(BareType {
|
Ok(Self {
|
||||||
attrs: input.call(Attribute::parse_outer)?,
|
attrs: input.call(Attribute::parse_outer)?,
|
||||||
type_token: input.parse()?,
|
type_token: input.parse()?,
|
||||||
ident: 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 once_cell::sync::Lazy;
|
||||||
use pdf_writer::types::DeviceNSubtype;
|
use pdf_writer::types::DeviceNSubtype;
|
||||||
use pdf_writer::{writers, Chunk, Dict, Filter, Name, Ref};
|
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::deflate;
|
||||||
use crate::page::{PageContext, Transforms};
|
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::types::{CidFontType, FontFlags, SystemInfo, UnicodeCmap};
|
||||||
use pdf_writer::{Filter, Finish, Name, Rect, Str};
|
use pdf_writer::{Filter, Finish, Name, Rect, Str};
|
||||||
use ttf_parser::{name_id, GlyphId, Tag};
|
use ttf_parser::{name_id, GlyphId, Tag};
|
||||||
use typst::font::Font;
|
use typst::text::Font;
|
||||||
use typst::util::SliceExt;
|
use typst::util::SliceExt;
|
||||||
use unicode_properties::{GeneralCategory, UnicodeGeneralCategory};
|
use unicode_properties::{GeneralCategory, UnicodeGeneralCategory};
|
||||||
|
|
||||||
|
@ -5,9 +5,10 @@ use ecow::{eco_format, EcoString};
|
|||||||
use pdf_writer::types::{ColorSpaceOperand, FunctionShadingType};
|
use pdf_writer::types::{ColorSpaceOperand, FunctionShadingType};
|
||||||
use pdf_writer::writers::StreamShadingType;
|
use pdf_writer::writers::StreamShadingType;
|
||||||
use pdf_writer::{Filter, Finish, Name, Ref};
|
use pdf_writer::{Filter, Finish, Name, Ref};
|
||||||
use typst::geom::{
|
use typst::layout::{Abs, Angle, Point, Quadrant, Ratio, Transform};
|
||||||
Abs, Angle, Color, ColorSpace, ConicGradient, Gradient, Numeric, Point, Quadrant,
|
use typst::util::Numeric;
|
||||||
Ratio, Relative, Transform, WeightedColor,
|
use typst::visualize::{
|
||||||
|
Color, ColorSpace, ConicGradient, Gradient, GradientRelative, WeightedColor,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::color::{ColorSpaceExt, PaintEncode, QuantizedColor};
|
use crate::color::{ColorSpaceExt, PaintEncode, QuantizedColor};
|
||||||
@ -301,8 +302,8 @@ fn register_gradient(
|
|||||||
transforms.size.y = Abs::pt(1.0);
|
transforms.size.y = Abs::pt(1.0);
|
||||||
}
|
}
|
||||||
let size = match gradient.unwrap_relative(on_text) {
|
let size = match gradient.unwrap_relative(on_text) {
|
||||||
Relative::Self_ => transforms.size,
|
GradientRelative::Self_ => transforms.size,
|
||||||
Relative::Parent => transforms.container_size,
|
GradientRelative::Parent => transforms.container_size,
|
||||||
};
|
};
|
||||||
|
|
||||||
let (offset_x, offset_y) = match gradient {
|
let (offset_x, offset_y) = match gradient {
|
||||||
@ -316,8 +317,8 @@ fn register_gradient(
|
|||||||
let rotation = gradient.angle().unwrap_or_else(Angle::zero);
|
let rotation = gradient.angle().unwrap_or_else(Angle::zero);
|
||||||
|
|
||||||
let transform = match gradient.unwrap_relative(on_text) {
|
let transform = match gradient.unwrap_relative(on_text) {
|
||||||
Relative::Self_ => transforms.transform,
|
GradientRelative::Self_ => transforms.transform,
|
||||||
Relative::Parent => transforms.container_transform,
|
GradientRelative::Parent => transforms.container_transform,
|
||||||
};
|
};
|
||||||
|
|
||||||
let scale_offset = match gradient {
|
let scale_offset = match gradient {
|
||||||
|
@ -3,9 +3,10 @@ use std::io::Cursor;
|
|||||||
|
|
||||||
use image::{DynamicImage, GenericImageView, Rgba};
|
use image::{DynamicImage, GenericImageView, Rgba};
|
||||||
use pdf_writer::{Chunk, Filter, Finish, Ref};
|
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::util::Deferred;
|
||||||
|
use typst::visualize::{
|
||||||
|
ColorSpace, Image, ImageKind, RasterFormat, RasterImage, SvgImage,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{deflate, PdfContext};
|
use crate::{deflate, PdfContext};
|
||||||
|
|
||||||
|
@ -16,13 +16,13 @@ use base64::Engine;
|
|||||||
use ecow::{eco_format, EcoString};
|
use ecow::{eco_format, EcoString};
|
||||||
use pdf_writer::types::Direction;
|
use pdf_writer::types::Direction;
|
||||||
use pdf_writer::{Finish, Name, Pdf, Ref, TextStr};
|
use pdf_writer::{Finish, Name, Pdf, Ref, TextStr};
|
||||||
use typst::doc::{Document, Lang};
|
use typst::foundations::Datetime;
|
||||||
use typst::eval::Datetime;
|
use typst::introspection::Introspector;
|
||||||
use typst::font::Font;
|
use typst::layout::{Abs, Dir, Em};
|
||||||
use typst::geom::{Abs, Dir, Em};
|
use typst::model::Document;
|
||||||
use typst::image::Image;
|
use typst::text::{Font, Lang};
|
||||||
use typst::model::Introspector;
|
|
||||||
use typst::util::Deferred;
|
use typst::util::Deferred;
|
||||||
|
use typst::visualize::Image;
|
||||||
use xmp_writer::{DateTime, LangId, RenditionClass, Timezone, XmpWriter};
|
use xmp_writer::{DateTime, LangId, RenditionClass, Timezone, XmpWriter};
|
||||||
|
|
||||||
use crate::color::ColorSpaces;
|
use crate::color::ColorSpaces;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
use pdf_writer::{Finish, Ref, TextStr};
|
use pdf_writer::{Finish, Ref, TextStr};
|
||||||
use typst::eval::{item, Smart};
|
use typst::foundations::{Content, NativeElement, Smart};
|
||||||
use typst::geom::Abs;
|
use typst::layout::Abs;
|
||||||
use typst::model::Content;
|
use typst::model::HeadingElem;
|
||||||
|
|
||||||
use crate::{AbsExt, PdfContext};
|
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
|
// Therefore, its next descendant must be added at its level, which is
|
||||||
// enforced in the manner shown below.
|
// enforced in the manner shown below.
|
||||||
let mut last_skipped_level = None;
|
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());
|
let leaf = HeadingNode::leaf((**heading).clone());
|
||||||
|
|
||||||
if leaf.bookmarked {
|
if leaf.bookmarked {
|
||||||
|
@ -8,16 +8,17 @@ use pdf_writer::types::{
|
|||||||
};
|
};
|
||||||
use pdf_writer::writers::PageLabel;
|
use pdf_writer::writers::PageLabel;
|
||||||
use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str, TextStr};
|
use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str, TextStr};
|
||||||
use typst::doc::{
|
use typst::introspection::Meta;
|
||||||
Destination, Frame, FrameItem, GroupItem, Meta, PdfPageLabel, PdfPageLabelStyle,
|
use typst::layout::{
|
||||||
TextItem,
|
Abs, Em, Frame, FrameItem, GroupItem, PdfPageLabel, PdfPageLabelStyle, Point, Ratio,
|
||||||
|
Size, Transform,
|
||||||
};
|
};
|
||||||
use typst::font::Font;
|
use typst::model::Destination;
|
||||||
use typst::geom::{
|
use typst::text::{Font, TextItem};
|
||||||
self, Abs, Em, FixedStroke, Geometry, LineCap, LineJoin, Numeric, Paint, Point,
|
use typst::util::Numeric;
|
||||||
Ratio, Shape, Size, Transform,
|
use typst::visualize::{
|
||||||
|
FixedStroke, Geometry, Image, LineCap, LineJoin, Paint, Path, PathItem, Shape,
|
||||||
};
|
};
|
||||||
use typst::image::Image;
|
|
||||||
|
|
||||||
use crate::color::PaintEncode;
|
use crate::color::PaintEncode;
|
||||||
use crate::extg::ExtGState;
|
use crate::extg::ExtGState;
|
||||||
@ -581,7 +582,7 @@ fn write_text(ctx: &mut PageContext, pos: Point, text: &TextItem) {
|
|||||||
adjustment = Em::zero();
|
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 >> 8) as u8);
|
||||||
encoded.push((cid & 0xff) 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.
|
/// 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 {
|
for elem in &path.0 {
|
||||||
match elem {
|
match elem {
|
||||||
geom::PathItem::MoveTo(p) => {
|
PathItem::MoveTo(p) => {
|
||||||
ctx.content.move_to(x + p.x.to_f32(), y + p.y.to_f32())
|
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())
|
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(),
|
x + p1.x.to_f32(),
|
||||||
y + p1.y.to_f32(),
|
y + p1.y.to_f32(),
|
||||||
x + p2.x.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(),
|
x + p3.x.to_f32(),
|
||||||
y + p3.y.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 resvg::tiny_skia::IntRect;
|
||||||
use tiny_skia as sk;
|
use tiny_skia as sk;
|
||||||
use ttf_parser::{GlyphId, OutlineBuilder};
|
use ttf_parser::{GlyphId, OutlineBuilder};
|
||||||
use typst::doc::{Frame, FrameItem, FrameKind, GroupItem, Meta, TextItem};
|
use typst::introspection::Meta;
|
||||||
use typst::font::Font;
|
use typst::layout::{
|
||||||
use typst::geom::{
|
Abs, Axes, Frame, FrameItem, FrameKind, GroupItem, Point, Ratio, Size, Transform,
|
||||||
self, Abs, Axes, Color, FixedStroke, Geometry, Gradient, LineCap, LineJoin, Paint,
|
};
|
||||||
PathItem, Point, Ratio, Relative, Shape, 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};
|
use usvg::{NodeExt, TreeParsing};
|
||||||
|
|
||||||
/// Export a frame into a raster image.
|
/// 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.
|
/// 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();
|
let mut builder = sk::PathBuilder::new();
|
||||||
for elem in &path.0 {
|
for elem in &path.0 {
|
||||||
match elem {
|
match elem {
|
||||||
@ -773,13 +775,13 @@ impl<'a> GradientSampler<'a> {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
let relative = gradient.unwrap_relative(on_text);
|
let relative = gradient.unwrap_relative(on_text);
|
||||||
let container_size = match relative {
|
let container_size = match relative {
|
||||||
Relative::Self_ => item_size,
|
GradientRelative::Self_ => item_size,
|
||||||
Relative::Parent => state.size,
|
GradientRelative::Parent => state.size,
|
||||||
};
|
};
|
||||||
|
|
||||||
let fill_transform = match relative {
|
let fill_transform = match relative {
|
||||||
Relative::Self_ => sk::Transform::identity(),
|
GradientRelative::Self_ => sk::Transform::identity(),
|
||||||
Relative::Parent => state.container_transform.invert().unwrap(),
|
GradientRelative::Parent => state.container_transform.invert().unwrap(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@ -857,13 +859,13 @@ fn to_sk_paint<'a>(
|
|||||||
Paint::Gradient(gradient) => {
|
Paint::Gradient(gradient) => {
|
||||||
let relative = gradient.unwrap_relative(on_text);
|
let relative = gradient.unwrap_relative(on_text);
|
||||||
let container_size = match relative {
|
let container_size = match relative {
|
||||||
Relative::Self_ => item_size,
|
GradientRelative::Self_ => item_size,
|
||||||
Relative::Parent => state.size,
|
GradientRelative::Parent => state.size,
|
||||||
};
|
};
|
||||||
|
|
||||||
let fill_transform = match relative {
|
let fill_transform = match relative {
|
||||||
Relative::Self_ => fill_transform.unwrap_or_default(),
|
GradientRelative::Self_ => fill_transform.unwrap_or_default(),
|
||||||
Relative::Parent => state
|
GradientRelative::Parent => state
|
||||||
.container_transform
|
.container_transform
|
||||||
.post_concat(state.transform.invert().unwrap()),
|
.post_concat(state.transform.invert().unwrap()),
|
||||||
};
|
};
|
||||||
|
@ -6,16 +6,18 @@ use std::io::Read;
|
|||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use ecow::{eco_format, EcoString};
|
use ecow::{eco_format, EcoString};
|
||||||
use ttf_parser::{GlyphId, OutlineBuilder};
|
use ttf_parser::{GlyphId, OutlineBuilder};
|
||||||
use typst::doc::{Frame, FrameItem, FrameKind, GroupItem, TextItem};
|
use typst::foundations::Repr;
|
||||||
use typst::eval::Repr;
|
use typst::layout::{
|
||||||
use typst::font::Font;
|
Abs, Angle, Axes, Frame, FrameItem, FrameKind, GroupItem, Point, Quadrant, Ratio,
|
||||||
use typst::geom::{
|
Size, Transform,
|
||||||
self, Abs, Angle, Axes, Color, FixedStroke, Geometry, Gradient, LineCap, LineJoin,
|
|
||||||
Paint, PathItem, Point, Quadrant, Ratio, RatioOrAngle, Relative, Shape, Size,
|
|
||||||
Transform,
|
|
||||||
};
|
};
|
||||||
use typst::image::{Image, ImageFormat, RasterFormat, VectorFormat};
|
use typst::text::{Font, TextItem};
|
||||||
use typst::util::hash128;
|
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;
|
use xmlwriter::XmlWriter;
|
||||||
|
|
||||||
/// The number of segments in a conic gradient.
|
/// The number of segments in a conic gradient.
|
||||||
@ -432,8 +434,8 @@ impl SVGRenderer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
match gradient.unwrap_relative(true) {
|
match gradient.unwrap_relative(true) {
|
||||||
Relative::Self_ => Transform::scale(Ratio::one(), Ratio::one()),
|
GradientRelative::Self_ => Transform::scale(Ratio::one(), Ratio::one()),
|
||||||
Relative::Parent => Transform::scale(
|
GradientRelative::Parent => Transform::scale(
|
||||||
Ratio::new(state.size.x.to_pt()),
|
Ratio::new(state.size.x.to_pt()),
|
||||||
Ratio::new(state.size.y.to_pt()),
|
Ratio::new(state.size.y.to_pt()),
|
||||||
)
|
)
|
||||||
@ -488,11 +490,11 @@ impl SVGRenderer {
|
|||||||
|
|
||||||
if let Paint::Gradient(gradient) = paint {
|
if let Paint::Gradient(gradient) = paint {
|
||||||
match gradient.unwrap_relative(false) {
|
match gradient.unwrap_relative(false) {
|
||||||
Relative::Self_ => Transform::scale(
|
GradientRelative::Self_ => Transform::scale(
|
||||||
Ratio::new(shape_size.x.to_pt()),
|
Ratio::new(shape_size.x.to_pt()),
|
||||||
Ratio::new(shape_size.y.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.x.to_pt()),
|
||||||
Ratio::new(state.size.y.to_pt()),
|
Ratio::new(state.size.y.to_pt()),
|
||||||
)
|
)
|
||||||
@ -517,8 +519,8 @@ impl SVGRenderer {
|
|||||||
|
|
||||||
if let Paint::Gradient(gradient) = paint {
|
if let Paint::Gradient(gradient) = paint {
|
||||||
match gradient.unwrap_relative(false) {
|
match gradient.unwrap_relative(false) {
|
||||||
Relative::Self_ => shape_size,
|
GradientRelative::Self_ => shape_size,
|
||||||
Relative::Parent => state.size,
|
GradientRelative::Parent => state.size,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
shape_size
|
shape_size
|
||||||
@ -1047,7 +1049,7 @@ fn convert_geometry_to_path(geometry: &Geometry) -> EcoString {
|
|||||||
builder.0
|
builder.0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_path(path: &geom::Path) -> EcoString {
|
fn convert_path(path: &Path) -> EcoString {
|
||||||
let mut builder = SvgPathBuilder::default();
|
let mut builder = SvgPathBuilder::default();
|
||||||
for item in &path.0 {
|
for item in &path.0 {
|
||||||
match item {
|
match item {
|
||||||
|
@ -11,7 +11,7 @@ use crate::{
|
|||||||
/// ultimately reparsed.
|
/// ultimately reparsed.
|
||||||
///
|
///
|
||||||
/// The high-level API for this function is
|
/// The high-level API for this function is
|
||||||
/// [`Source::edit`](super::Source::edit).
|
/// [`Source::edit`](crate::Source::edit).
|
||||||
pub fn reparse(
|
pub fn reparse(
|
||||||
root: &mut SyntaxNode,
|
root: &mut SyntaxNode,
|
||||||
text: &str,
|
text: &str,
|
||||||
|
@ -7,7 +7,7 @@ use crate::FileId;
|
|||||||
/// A unique identifier for a syntax node.
|
/// A unique identifier for a syntax node.
|
||||||
///
|
///
|
||||||
/// This is used throughout the compiler to track which source section an error
|
/// 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.
|
/// range for user facing display.
|
||||||
///
|
///
|
||||||
/// During editing, the span values stay mostly stable, even for nodes behind an
|
/// During editing, the span values stay mostly stable, even for nodes behind an
|
||||||
@ -76,7 +76,7 @@ impl Span {
|
|||||||
Some(FileId::from_raw(bits))
|
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 {
|
pub const fn number(self) -> u64 {
|
||||||
self.0.get() & ((1 << Self::BITS) - 1)
|
self.0.get() & ((1 << Self::BITS) - 1)
|
||||||
}
|
}
|
||||||
|
@ -18,14 +18,26 @@ bench = false
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
typst-macros = { workspace = true }
|
typst-macros = { workspace = true }
|
||||||
typst-syntax = { workspace = true }
|
typst-syntax = { workspace = true }
|
||||||
|
az = { workspace = true }
|
||||||
bitflags = { workspace = true }
|
bitflags = { workspace = true }
|
||||||
|
chinese-number = { workspace = true }
|
||||||
|
ciborium = { workspace = true }
|
||||||
comemo = { workspace = true }
|
comemo = { workspace = true }
|
||||||
|
csv = { workspace = true }
|
||||||
ecow = { workspace = true}
|
ecow = { workspace = true}
|
||||||
fontdb = { 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 }
|
image = { workspace = true }
|
||||||
indexmap = { workspace = true }
|
indexmap = { workspace = true }
|
||||||
kurbo = { workspace = true }
|
kurbo = { workspace = true }
|
||||||
lasso = { workspace = true }
|
lasso = { workspace = true }
|
||||||
|
lipsum = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
once_cell = { workspace = true }
|
once_cell = { workspace = true }
|
||||||
palette = { workspace = true }
|
palette = { workspace = true }
|
||||||
@ -34,13 +46,19 @@ regex = { workspace = true }
|
|||||||
roxmltree = { workspace = true }
|
roxmltree = { workspace = true }
|
||||||
rustybuzz = { workspace = true }
|
rustybuzz = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
serde_yaml = { workspace = true }
|
||||||
siphasher = { workspace = true }
|
siphasher = { workspace = true }
|
||||||
smallvec = { workspace = true }
|
smallvec = { workspace = true }
|
||||||
|
syntect = { workspace = true }
|
||||||
time = { workspace = true }
|
time = { workspace = true }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
ttf-parser = { workspace = true }
|
ttf-parser = { workspace = true }
|
||||||
|
typed-arena = { workspace = true }
|
||||||
|
unicode-bidi = { workspace = true }
|
||||||
unicode-math-class = { workspace = true }
|
unicode-math-class = { workspace = true }
|
||||||
|
unicode-script = { workspace = true }
|
||||||
unicode-segmentation = { workspace = true }
|
unicode-segmentation = { workspace = true }
|
||||||
usvg = { workspace = true }
|
usvg = { workspace = true }
|
||||||
wasmi = { workspace = true }
|
wasmi = { workspace = true }
|
||||||
|
@ -42,10 +42,16 @@ macro_rules! __bail {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[doc(inline)]
|
#[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)]
|
#[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`.
|
/// Construct an [`EcoString`] or [`SourceDiagnostic`] with severity `Error`.
|
||||||
#[macro_export]
|
#[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).
|
/// A part of a diagnostic's [trace](SourceDiagnostic::trace).
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
pub enum Tracepoint {
|
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 ecow::eco_format;
|
||||||
|
|
||||||
use crate::diag::{bail, StrResult};
|
use crate::diag::{bail, At, SourceResult, StrResult};
|
||||||
use crate::eval::{format_str, item, IntoValue, Regex, Repr, Smart, Value};
|
use crate::eval::{access_dict, Access, Eval, Vm};
|
||||||
use crate::geom::{Align, Length, Numeric, Rel, Stroke};
|
use crate::foundations::{format_str, Datetime, IntoValue, Regex, Repr, Smart, Value};
|
||||||
use 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.
|
/// Bail with a type mismatch error.
|
||||||
macro_rules! mismatch {
|
macro_rules! mismatch {
|
||||||
@ -18,6 +110,7 @@ macro_rules! mismatch {
|
|||||||
|
|
||||||
/// Join a value with another value.
|
/// Join a value with another value.
|
||||||
pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> {
|
pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||||
|
use Value::*;
|
||||||
Ok(match (lhs, rhs) {
|
Ok(match (lhs, rhs) {
|
||||||
(a, None) => a,
|
(a, None) => a,
|
||||||
(None, b) => b,
|
(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}")),
|
(Symbol(a), Str(b)) => Str(format_str!("{a}{b}")),
|
||||||
(Bytes(a), Bytes(b)) => Bytes(a + b),
|
(Bytes(a), Bytes(b)) => Bytes(a + b),
|
||||||
(Content(a), Content(b)) => Content(a + b),
|
(Content(a), Content(b)) => Content(a + b),
|
||||||
(Content(a), Symbol(b)) => Content(a + item!(text)(b.get().into())),
|
(Content(a), Symbol(b)) => Content(a + TextElem::packed(b.get())),
|
||||||
(Content(a), Str(b)) => Content(a + item!(text)(b.into())),
|
(Content(a), Str(b)) => Content(a + TextElem::packed(b)),
|
||||||
(Str(a), Content(b)) => Content(item!(text)(a.into()) + b),
|
(Str(a), Content(b)) => Content(TextElem::packed(a) + b),
|
||||||
(Symbol(a), Content(b)) => Content(item!(text)(a.get().into()) + b),
|
(Symbol(a), Content(b)) => Content(TextElem::packed(a.get()) + b),
|
||||||
(Array(a), Array(b)) => Array(a + b),
|
(Array(a), Array(b)) => Array(a + b),
|
||||||
(Dict(a), Dict(b)) => Dict(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.
|
/// Apply the unary plus operator to a value.
|
||||||
pub fn pos(value: Value) -> StrResult<Value> {
|
pub fn pos(value: Value) -> StrResult<Value> {
|
||||||
|
use Value::*;
|
||||||
Ok(match value {
|
Ok(match value {
|
||||||
Int(v) => Int(v),
|
Int(v) => Int(v),
|
||||||
Float(v) => Float(v),
|
Float(v) => Float(v),
|
||||||
@ -68,6 +162,7 @@ pub fn pos(value: Value) -> StrResult<Value> {
|
|||||||
|
|
||||||
/// Compute the negation of a value.
|
/// Compute the negation of a value.
|
||||||
pub fn neg(value: Value) -> StrResult<Value> {
|
pub fn neg(value: Value) -> StrResult<Value> {
|
||||||
|
use Value::*;
|
||||||
Ok(match value {
|
Ok(match value {
|
||||||
Int(v) => Int(v.checked_neg().ok_or_else(too_large)?),
|
Int(v) => Int(v.checked_neg().ok_or_else(too_large)?),
|
||||||
Float(v) => Float(-v),
|
Float(v) => Float(-v),
|
||||||
@ -84,6 +179,7 @@ pub fn neg(value: Value) -> StrResult<Value> {
|
|||||||
|
|
||||||
/// Compute the sum of two values.
|
/// Compute the sum of two values.
|
||||||
pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
|
pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||||
|
use Value::*;
|
||||||
Ok(match (lhs, rhs) {
|
Ok(match (lhs, rhs) {
|
||||||
(a, None) => a,
|
(a, None) => a,
|
||||||
(None, b) => b,
|
(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}")),
|
(Symbol(a), Str(b)) => Str(format_str!("{a}{b}")),
|
||||||
(Bytes(a), Bytes(b)) => Bytes(a + b),
|
(Bytes(a), Bytes(b)) => Bytes(a + b),
|
||||||
(Content(a), Content(b)) => Content(a + b),
|
(Content(a), Content(b)) => Content(a + b),
|
||||||
(Content(a), Symbol(b)) => Content(a + item!(text)(b.get().into())),
|
(Content(a), Symbol(b)) => Content(a + TextElem::packed(b.get())),
|
||||||
(Content(a), Str(b)) => Content(a + item!(text)(b.into())),
|
(Content(a), Str(b)) => Content(a + TextElem::packed(b)),
|
||||||
(Str(a), Content(b)) => Content(item!(text)(a.into()) + b),
|
(Str(a), Content(b)) => Content(TextElem::packed(a) + b),
|
||||||
(Symbol(a), Content(b)) => Content(item!(text)(a.get().into()) + b),
|
(Symbol(a), Content(b)) => Content(TextElem::packed(a.get()) + b),
|
||||||
|
|
||||||
(Array(a), Array(b)) => Array(a + b),
|
(Array(a), Array(b)) => Array(a + b),
|
||||||
(Dict(a), Dict(b)) => Dict(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.
|
/// Compute the difference of two values.
|
||||||
pub fn sub(lhs: Value, rhs: Value) -> StrResult<Value> {
|
pub fn sub(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||||
|
use Value::*;
|
||||||
Ok(match (lhs, rhs) {
|
Ok(match (lhs, rhs) {
|
||||||
(Int(a), Int(b)) => Int(a.checked_sub(b).ok_or_else(too_large)?),
|
(Int(a), Int(b)) => Int(a.checked_sub(b).ok_or_else(too_large)?),
|
||||||
(Int(a), Float(b)) => Float(a as f64 - b),
|
(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.
|
/// Compute the product of two values.
|
||||||
pub fn mul(lhs: Value, rhs: Value) -> StrResult<Value> {
|
pub fn mul(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||||
|
use Value::*;
|
||||||
Ok(match (lhs, rhs) {
|
Ok(match (lhs, rhs) {
|
||||||
(Int(a), Int(b)) => Int(a.checked_mul(b).ok_or_else(too_large)?),
|
(Int(a), Int(b)) => Int(a.checked_mul(b).ok_or_else(too_large)?),
|
||||||
(Int(a), Float(b)) => Float(a as f64 * b),
|
(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.
|
/// Compute the quotient of two values.
|
||||||
pub fn div(lhs: Value, rhs: Value) -> StrResult<Value> {
|
pub fn div(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||||
|
use Value::*;
|
||||||
if is_zero(&rhs) {
|
if is_zero(&rhs) {
|
||||||
bail!("cannot divide by zero");
|
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.
|
/// Whether a value is a numeric zero.
|
||||||
fn is_zero(v: &Value) -> bool {
|
fn is_zero(v: &Value) -> bool {
|
||||||
|
use Value::*;
|
||||||
match *v {
|
match *v {
|
||||||
Int(v) => v == 0,
|
Int(v) => v == 0,
|
||||||
Float(v) => v == 0.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.
|
/// Compute the logical "not" of a value.
|
||||||
pub fn not(value: Value) -> StrResult<Value> {
|
pub fn not(value: Value) -> StrResult<Value> {
|
||||||
match value {
|
match value {
|
||||||
Bool(b) => Ok(Bool(!b)),
|
Value::Bool(b) => Ok(Value::Bool(!b)),
|
||||||
v => mismatch!("cannot apply 'not' to {}", v),
|
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.
|
/// Compute the logical "and" of two values.
|
||||||
pub fn and(lhs: Value, rhs: Value) -> StrResult<Value> {
|
pub fn and(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||||
match (lhs, rhs) {
|
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),
|
(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.
|
/// Compute the logical "or" of two values.
|
||||||
pub fn or(lhs: Value, rhs: Value) -> StrResult<Value> {
|
pub fn or(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||||
match (lhs, rhs) {
|
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),
|
(a, b) => mismatch!("cannot apply 'or' to {} and {}", a, b),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute whether two values are equal.
|
/// Compute whether two values are equal.
|
||||||
pub fn eq(lhs: Value, rhs: Value) -> StrResult<Value> {
|
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.
|
/// Compute whether two values are unequal.
|
||||||
pub fn neq(lhs: Value, rhs: Value) -> StrResult<Value> {
|
pub fn neq(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||||
Ok(Bool(!equal(&lhs, &rhs)))
|
Ok(Value::Bool(!equal(&lhs, &rhs)))
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! comparison {
|
macro_rules! comparison {
|
||||||
@ -358,7 +458,7 @@ macro_rules! comparison {
|
|||||||
/// Compute how a value compares with another value.
|
/// Compute how a value compares with another value.
|
||||||
pub fn $name(lhs: Value, rhs: Value) -> StrResult<Value> {
|
pub fn $name(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||||
let ordering = compare(&lhs, &rhs)?;
|
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.
|
/// Determine whether two values are equal.
|
||||||
pub fn equal(lhs: &Value, rhs: &Value) -> bool {
|
pub fn equal(lhs: &Value, rhs: &Value) -> bool {
|
||||||
|
use Value::*;
|
||||||
match (lhs, rhs) {
|
match (lhs, rhs) {
|
||||||
// Compare reflexively.
|
// Compare reflexively.
|
||||||
(None, None) => true,
|
(None, None) => true,
|
||||||
@ -418,6 +519,7 @@ pub fn equal(lhs: &Value, rhs: &Value) -> bool {
|
|||||||
|
|
||||||
/// Compare two values.
|
/// Compare two values.
|
||||||
pub fn compare(lhs: &Value, rhs: &Value) -> StrResult<Ordering> {
|
pub fn compare(lhs: &Value, rhs: &Value) -> StrResult<Ordering> {
|
||||||
|
use Value::*;
|
||||||
Ok(match (lhs, rhs) {
|
Ok(match (lhs, rhs) {
|
||||||
(Bool(a), Bool(b)) => a.cmp(b),
|
(Bool(a), Bool(b)) => a.cmp(b),
|
||||||
(Int(a), Int(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.
|
/// 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)
|
a.partial_cmp(b)
|
||||||
.ok_or_else(|| eco_format!("cannot compare {} and {}", a.kind(), b.kind()))
|
.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.
|
/// Test whether one value is "in" another one.
|
||||||
pub fn in_(lhs: Value, rhs: Value) -> StrResult<Value> {
|
pub fn in_(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||||
if let Some(b) = contains(&lhs, &rhs) {
|
if let Some(b) = contains(&lhs, &rhs) {
|
||||||
Ok(Bool(b))
|
Ok(Value::Bool(b))
|
||||||
} else {
|
} else {
|
||||||
mismatch!("cannot apply 'in' to {} and {}", lhs, rhs)
|
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.
|
/// Test whether one value is "not in" another one.
|
||||||
pub fn not_in(lhs: Value, rhs: Value) -> StrResult<Value> {
|
pub fn not_in(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||||
if let Some(b) = contains(&lhs, &rhs) {
|
if let Some(b) = contains(&lhs, &rhs) {
|
||||||
Ok(Bool(!b))
|
Ok(Value::Bool(!b))
|
||||||
} else {
|
} else {
|
||||||
mismatch!("cannot apply 'not in' to {} and {}", lhs, rhs)
|
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.
|
/// Test for containment.
|
||||||
pub fn contains(lhs: &Value, rhs: &Value) -> Option<bool> {
|
pub fn contains(lhs: &Value, rhs: &Value) -> Option<bool> {
|
||||||
|
use Value::*;
|
||||||
match (lhs, rhs) {
|
match (lhs, rhs) {
|
||||||
(Str(a), Str(b)) => Some(b.as_str().contains(a.as_str())),
|
(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)),
|
(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 ecow::EcoVec;
|
||||||
|
|
||||||
use crate::diag::SourceDiagnostic;
|
use crate::diag::SourceDiagnostic;
|
||||||
use crate::eval::Value;
|
use crate::foundations::Value;
|
||||||
use crate::syntax::{FileId, Span};
|
use crate::syntax::{FileId, Span};
|
||||||
use crate::util::hash128;
|
use crate::util::hash128;
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ impl Tracer {
|
|||||||
|
|
||||||
#[comemo::track]
|
#[comemo::track]
|
||||||
impl Tracer {
|
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> {
|
pub fn inspected(&self, id: FileId) -> Option<Span> {
|
||||||
if self.inspected.and_then(Span::id) == Some(id) {
|
if self.inspected.and_then(Span::id) == Some(id) {
|
||||||
self.inspected
|
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 ecow::{eco_format, eco_vec, EcoString, EcoVec};
|
||||||
|
|
||||||
use crate::diag::{bail, At, SourceDiagnostic, SourceResult};
|
use crate::diag::{bail, At, SourceDiagnostic, SourceResult};
|
||||||
use crate::eval::{
|
use crate::foundations::{
|
||||||
func, repr, scope, ty, Array, Dict, FromValue, IntoValue, Repr, Str, Value,
|
func, repr, scope, ty, Array, Dict, FromValue, IntoValue, Repr, Str, Value,
|
||||||
};
|
};
|
||||||
use crate::syntax::{Span, Spanned};
|
use crate::syntax::{Span, Spanned};
|
@ -8,9 +8,10 @@ use serde::{Deserialize, Serialize};
|
|||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::diag::{At, SourceResult, StrResult};
|
use crate::diag::{At, SourceResult, StrResult};
|
||||||
use crate::eval::{
|
use crate::eval::{ops, Vm};
|
||||||
cast, func, ops, repr, scope, ty, Args, Bytes, CastInfo, FromValue, Func, IntoValue,
|
use crate::foundations::{
|
||||||
Reflect, Repr, Value, Version, Vm,
|
cast, func, repr, scope, ty, Args, Bytes, CastInfo, FromValue, Func, IntoValue,
|
||||||
|
Reflect, Repr, Value, Version,
|
||||||
};
|
};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
|
|
||||||
@ -19,15 +20,15 @@ use crate::syntax::Span;
|
|||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
macro_rules! __array {
|
macro_rules! __array {
|
||||||
($value:expr; $count:expr) => {
|
($value:expr; $count:expr) => {
|
||||||
$crate::eval::Array::from($crate::eval::eco_vec![
|
$crate::foundations::Array::from($crate::foundations::eco_vec![
|
||||||
$crate::eval::IntoValue::into_value($value);
|
$crate::foundations::IntoValue::into_value($value);
|
||||||
$count
|
$count
|
||||||
])
|
])
|
||||||
};
|
};
|
||||||
|
|
||||||
($($value:expr),* $(,)?) => {
|
($($value:expr),* $(,)?) => {
|
||||||
$crate::eval::Array::from($crate::eval::eco_vec![$(
|
$crate::foundations::Array::from($crate::foundations::eco_vec![$(
|
||||||
$crate::eval::IntoValue::into_value($value)
|
$crate::foundations::IntoValue::into_value($value)
|
||||||
),*])
|
),*])
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -35,9 +36,6 @@ macro_rules! __array {
|
|||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use crate::__array as array;
|
pub use crate::__array as array;
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub use ecow::eco_vec;
|
|
||||||
|
|
||||||
/// A sequence of values.
|
/// A sequence of values.
|
||||||
///
|
///
|
||||||
/// You can construct an array by enclosing a comma-separated 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| {
|
vec.make_mut().sort_by(|a, b| {
|
||||||
// Until we get `try` blocks :)
|
// Until we get `try` blocks :)
|
||||||
match (key_of(a.clone()), key_of(b.clone())) {
|
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() {
|
if result.is_ok() {
|
||||||
result = Err(err).at(span);
|
result = Err(err).at(span);
|
||||||
}
|
}
|
||||||
@ -790,7 +788,7 @@ impl Array {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for second in out.iter() {
|
for second in out.iter() {
|
||||||
if super::ops::equal(&key, &key_of(second.clone())?) {
|
if ops::equal(&key, &key_of(second.clone())?) {
|
||||||
continue 'outer;
|
continue 'outer;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,8 +2,10 @@ use ecow::EcoString;
|
|||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::eval::{ty, CastInfo, FromValue, IntoValue, Reflect, Repr, Type, Value};
|
use crate::foundations::{
|
||||||
use crate::model::{Fold, Resolve, StyleChain};
|
ty, CastInfo, Fold, FromValue, IntoValue, Reflect, Repr, Resolve, StyleChain, Type,
|
||||||
|
Value,
|
||||||
|
};
|
||||||
|
|
||||||
/// A value that indicates a smart default.
|
/// A value that indicates a smart default.
|
||||||
///
|
///
|
@ -1,6 +1,6 @@
|
|||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
|
|
||||||
use super::{ty, Repr};
|
use crate::foundations::{ty, Repr};
|
||||||
|
|
||||||
/// A type with two states.
|
/// A type with two states.
|
||||||
///
|
///
|
@ -8,7 +8,7 @@ use ecow::{eco_format, EcoString};
|
|||||||
use serde::{Serialize, Serializer};
|
use serde::{Serialize, Serializer};
|
||||||
|
|
||||||
use crate::diag::{bail, StrResult};
|
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.
|
/// A sequence of bytes.
|
||||||
///
|
///
|
@ -4,20 +4,15 @@ use std::cmp;
|
|||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::ops::{Div, Rem};
|
use std::ops::{Div, Rem};
|
||||||
|
|
||||||
use typst::eval::{Module, Scope};
|
use crate::diag::{bail, At, SourceResult, StrResult};
|
||||||
|
use crate::eval::ops;
|
||||||
use crate::prelude::*;
|
use crate::foundations::{cast, func, IntoValue, Module, Scope, Value};
|
||||||
|
use crate::layout::{Angle, Fr, Length, Ratio};
|
||||||
/// Hook up all calculation definitions.
|
use crate::syntax::{Span, Spanned};
|
||||||
pub(super) fn define(global: &mut Scope) {
|
|
||||||
global.category("calculate");
|
|
||||||
global.define_module(module());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A module with calculation definitions.
|
/// A module with calculation definitions.
|
||||||
fn module() -> Module {
|
pub fn module() -> Module {
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
scope.category("calculate");
|
|
||||||
scope.define_func::<abs>();
|
scope.define_func::<abs>();
|
||||||
scope.define_func::<pow>();
|
scope.define_func::<pow>();
|
||||||
scope.define_func::<exp>();
|
scope.define_func::<exp>();
|
||||||
@ -747,7 +742,7 @@ fn minmax(
|
|||||||
};
|
};
|
||||||
|
|
||||||
for Spanned { v, span } in iter {
|
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 {
|
if ordering == goal {
|
||||||
extremum = v;
|
extremum = v;
|
||||||
}
|
}
|
@ -1,5 +1,3 @@
|
|||||||
pub use typst_macros::{cast, Cast};
|
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
@ -11,9 +9,12 @@ use smallvec::SmallVec;
|
|||||||
use unicode_math_class::MathClass;
|
use unicode_math_class::MathClass;
|
||||||
|
|
||||||
use crate::diag::{At, SourceResult, StrResult};
|
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};
|
use crate::syntax::{Span, Spanned};
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use typst_macros::{cast, Cast};
|
||||||
|
|
||||||
/// Determine details of a type.
|
/// Determine details of a type.
|
||||||
///
|
///
|
||||||
/// Type casting works as follows:
|
/// Type casting works as follows:
|
@ -7,19 +7,20 @@ use std::sync::Arc;
|
|||||||
use comemo::Prehashed;
|
use comemo::Prehashed;
|
||||||
use ecow::{eco_format, EcoString};
|
use ecow::{eco_format, EcoString};
|
||||||
use serde::{Serialize, Serializer};
|
use serde::{Serialize, Serializer};
|
||||||
use smallvec::SmallVec;
|
use smallvec::smallvec;
|
||||||
use typst_macros::elem;
|
|
||||||
|
|
||||||
use crate::diag::{SourceResult, StrResult};
|
use crate::diag::{SourceResult, StrResult};
|
||||||
use crate::doc::Meta;
|
use crate::eval::Vm;
|
||||||
use crate::eval::{
|
use crate::foundations::{
|
||||||
func, repr, scope, ty, Dict, FromValue, IntoValue, Repr, Str, Value, Vm,
|
elem, func, scope, ty, Dict, Element, FromValue, Guard, IntoValue, Label,
|
||||||
};
|
NativeElement, Recipe, Repr, Selector, Str, Style, Styles, Value,
|
||||||
use crate::model::{
|
|
||||||
Behave, Behaviour, Element, Guard, Label, Location, NativeElement, Recipe, Selector,
|
|
||||||
Style, Styles,
|
|
||||||
};
|
};
|
||||||
|
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::syntax::Span;
|
||||||
|
use crate::text::UnderlineElem;
|
||||||
|
use crate::util::fat;
|
||||||
|
|
||||||
/// A piece of document content.
|
/// 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]
|
#[scope]
|
||||||
impl Content {
|
impl Content {
|
||||||
/// The content's element function. This function can be used to create the element
|
/// The content's element function. This function can be used to create the element
|
||||||
@ -690,7 +742,7 @@ impl Repr for SequenceElem {
|
|||||||
} else {
|
} else {
|
||||||
eco_format!(
|
eco_format!(
|
||||||
"[{}]",
|
"[{}]",
|
||||||
repr::pretty_array_like(
|
crate::foundations::repr::pretty_array_like(
|
||||||
&self.children.iter().map(|c| c.0.repr()).collect::<Vec<_>>(),
|
&self.children.iter().map(|c| c.0.repr()).collect::<Vec<_>>(),
|
||||||
false
|
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.
|
/// Tries to extract the plain-text representation of the element.
|
||||||
pub trait PlainText {
|
pub trait PlainText {
|
||||||
/// Write this element's plain text into the given buffer.
|
/// Write this element's plain text into the given buffer.
|
||||||
@ -743,7 +780,7 @@ pub trait PlainText {
|
|||||||
|
|
||||||
/// The missing field access error message.
|
/// The missing field access error message.
|
||||||
#[cold]
|
#[cold]
|
||||||
pub fn missing_field(field: &str) -> EcoString {
|
fn missing_field(field: &str) -> EcoString {
|
||||||
eco_format!("content does not contain field {}", field.repr())
|
eco_format!("content does not contain field {}", field.repr())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -756,66 +793,3 @@ fn missing_field_no_default(field: &str) -> EcoString {
|
|||||||
field.repr()
|
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::cmp::Ordering;
|
||||||
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::ops::{Add, Sub};
|
use std::ops::{Add, Sub};
|
||||||
|
|
||||||
@ -10,8 +8,9 @@ use time::macros::format_description;
|
|||||||
use time::{format_description, Month, PrimitiveDateTime};
|
use time::{format_description, Month, PrimitiveDateTime};
|
||||||
|
|
||||||
use crate::diag::{bail, StrResult};
|
use crate::diag::{bail, StrResult};
|
||||||
use crate::eval::{
|
use crate::eval::Vm;
|
||||||
cast, func, repr, scope, ty, Dict, Duration, Repr, Smart, Str, Value, Vm,
|
use crate::foundations::{
|
||||||
|
cast, func, repr, scope, ty, Dict, Duration, Repr, Smart, Str, Value,
|
||||||
};
|
};
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
@ -238,26 +237,26 @@ impl Datetime {
|
|||||||
pub fn construct(
|
pub fn construct(
|
||||||
/// The year of the datetime.
|
/// The year of the datetime.
|
||||||
#[named]
|
#[named]
|
||||||
year: Option<YearComponent>,
|
year: Option<i32>,
|
||||||
/// The month of the datetime.
|
/// The month of the datetime.
|
||||||
#[named]
|
#[named]
|
||||||
month: Option<MonthComponent>,
|
month: Option<Month>,
|
||||||
/// The day of the datetime.
|
/// The day of the datetime.
|
||||||
#[named]
|
#[named]
|
||||||
day: Option<DayComponent>,
|
day: Option<u8>,
|
||||||
/// The hour of the datetime.
|
/// The hour of the datetime.
|
||||||
#[named]
|
#[named]
|
||||||
hour: Option<HourComponent>,
|
hour: Option<u8>,
|
||||||
/// The minute of the datetime.
|
/// The minute of the datetime.
|
||||||
#[named]
|
#[named]
|
||||||
minute: Option<MinuteComponent>,
|
minute: Option<u8>,
|
||||||
/// The second of the datetime.
|
/// The second of the datetime.
|
||||||
#[named]
|
#[named]
|
||||||
second: Option<SecondComponent>,
|
second: Option<u8>,
|
||||||
) -> StrResult<Datetime> {
|
) -> StrResult<Datetime> {
|
||||||
let time = match (hour, minute, second) {
|
let time = match (hour, minute, second) {
|
||||||
(Some(hour), Some(minute), Some(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),
|
Ok(time) => Some(time),
|
||||||
Err(_) => bail!("time is invalid"),
|
Err(_) => bail!("time is invalid"),
|
||||||
}
|
}
|
||||||
@ -268,7 +267,7 @@ impl Datetime {
|
|||||||
|
|
||||||
let date = match (year, month, day) {
|
let date = match (year, month, day) {
|
||||||
(Some(year), Some(month), Some(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),
|
Ok(date) => Some(date),
|
||||||
Err(_) => bail!("date is invalid"),
|
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.
|
/// A format in which a datetime can be displayed.
|
||||||
pub struct DisplayPattern(Str, format_description::OwnedFormatItem);
|
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.
|
/// Format the `Format` error of the time crate in an appropriate way.
|
||||||
fn format_time_format_error(error: Format) -> EcoString {
|
fn format_time_format_error(error: Format) -> EcoString {
|
||||||
match error {
|
match error {
|
@ -8,7 +8,7 @@ use indexmap::IndexMap;
|
|||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
use crate::diag::StrResult;
|
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::syntax::is_ident;
|
||||||
use crate::util::ArcExt;
|
use crate::util::ArcExt;
|
||||||
|
|
||||||
@ -18,9 +18,9 @@ use crate::util::ArcExt;
|
|||||||
macro_rules! __dict {
|
macro_rules! __dict {
|
||||||
($($key:expr => $value:expr),* $(,)?) => {{
|
($($key:expr => $value:expr),* $(,)?) => {{
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
let mut map = $crate::eval::IndexMap::new();
|
let mut map = $crate::foundations::IndexMap::new();
|
||||||
$(map.insert($key.into(), $crate::eval::IntoValue::into_value($value));)*
|
$(map.insert($key.into(), $crate::foundations::IntoValue::into_value($value));)*
|
||||||
$crate::eval::Dict::from(map)
|
$crate::foundations::Dict::from(map)
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,10 @@
|
|||||||
use ecow::{eco_format, EcoString};
|
|
||||||
|
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::ops::{Add, Div, Mul, Neg, Sub};
|
use std::ops::{Add, Div, Mul, Neg, Sub};
|
||||||
|
|
||||||
|
use ecow::{eco_format, EcoString};
|
||||||
use time::ext::NumericalDuration;
|
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.
|
/// Represents a positive or negative span of time.
|
||||||
#[ty(scope)]
|
#[ty(scope)]
|
@ -1,4 +1,5 @@
|
|||||||
use std::any::{Any, TypeId};
|
use std::any::{Any, TypeId};
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::fmt::{self, Debug};
|
use std::fmt::{self, Debug};
|
||||||
use std::hash::Hasher;
|
use std::hash::Hasher;
|
||||||
@ -8,14 +9,21 @@ use ecow::EcoString;
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use super::{Content, Selector, Styles};
|
|
||||||
use crate::diag::{SourceResult, StrResult};
|
use crate::diag::{SourceResult, StrResult};
|
||||||
use crate::doc::{Lang, Region};
|
use crate::eval::Vm;
|
||||||
use crate::eval::{cast, Args, Dict, Func, ParamInfo, Repr, Scope, Value, Vm};
|
use crate::foundations::{
|
||||||
use crate::model::{Guard, Label, Location};
|
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::syntax::Span;
|
||||||
|
use crate::text::{Lang, Region};
|
||||||
use crate::util::Static;
|
use crate::util::Static;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use typst_macros::elem;
|
||||||
|
|
||||||
/// A document element.
|
/// A document element.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct Element(Static<NativeElementData>);
|
pub struct Element(Static<NativeElementData>);
|
||||||
@ -93,8 +101,8 @@ impl Element {
|
|||||||
Selector::Elem(self, None)
|
Selector::Elem(self, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a selector for this element, filtering for those
|
/// Create a selector for this element, filtering for those that
|
||||||
/// that [fields](super::Content::field) match the given argument.
|
/// [fields](crate::foundations::Content::field) match the given argument.
|
||||||
pub fn where_(self, fields: SmallVec<[(u8, Value); 1]>) -> Selector {
|
pub fn where_(self, fields: SmallVec<[(u8, Value); 1]>) -> Selector {
|
||||||
Selector::Elem(self, Some(fields))
|
Selector::Elem(self, Some(fields))
|
||||||
}
|
}
|
||||||
@ -308,8 +316,67 @@ cast! {
|
|||||||
self => Element::from(self).into_value(),
|
self => Element::from(self).into_value(),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The named with which an element is referenced.
|
/// Synthesize fields on an element. This happens before execution of any show
|
||||||
pub trait LocalName {
|
/// rule.
|
||||||
/// Get the name in the given language and (optionally) region.
|
pub trait Synthesize {
|
||||||
fn local_name(lang: Lang, region: Option<Region>) -> &'static str;
|
/// 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 ecow::{eco_format, EcoString};
|
||||||
|
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::eval::{IntoValue, Type, Value, Version};
|
use crate::foundations::{IntoValue, Type, Value, Version};
|
||||||
use crate::geom::{Align, Length, Rel, Stroke};
|
use crate::layout::{Align, Length, Rel};
|
||||||
|
use crate::visualize::Stroke;
|
||||||
|
|
||||||
/// Try to access a field on a value.
|
/// Try to access a field on a value.
|
||||||
///
|
///
|
@ -2,8 +2,8 @@ use std::num::ParseFloatError;
|
|||||||
|
|
||||||
use ecow::{eco_format, EcoString};
|
use ecow::{eco_format, EcoString};
|
||||||
|
|
||||||
use crate::eval::{cast, func, repr, scope, ty, Repr, Str};
|
use crate::foundations::{cast, func, repr, scope, ty, Repr, Str};
|
||||||
use crate::geom::Ratio;
|
use crate::layout::Ratio;
|
||||||
|
|
||||||
/// A floating-point number.
|
/// A floating-point number.
|
||||||
///
|
///
|
@ -1,22 +1,20 @@
|
|||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use comemo::{Prehashed, Tracked, TrackedMut};
|
use comemo::{Prehashed, TrackedMut};
|
||||||
use ecow::{eco_format, EcoString};
|
use ecow::{eco_format, EcoString};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use crate::diag::{bail, HintedStrResult, SourceResult, StrResult};
|
use crate::diag::{bail, SourceResult, StrResult};
|
||||||
use crate::eval::{
|
use crate::eval::{Route, Vm};
|
||||||
cast, scope, ty, Args, CastInfo, Eval, FlowEvent, IntoValue, Route, Scope, Scopes,
|
use crate::foundations::{
|
||||||
Tracer, Type, Value, Vm,
|
cast, repr, scope, ty, Args, CastInfo, Content, Element, IntoValue, Scope, Scopes,
|
||||||
|
Selector, Type, Value,
|
||||||
};
|
};
|
||||||
use crate::model::{
|
use crate::introspection::Locator;
|
||||||
Content, DelayedErrors, Element, Introspector, Locator, Selector, Vt,
|
use crate::layout::Vt;
|
||||||
};
|
use crate::syntax::{ast, FileId, Span, SyntaxNode};
|
||||||
use crate::syntax::ast::{self, AstNode};
|
|
||||||
use crate::syntax::{FileId, Span, SyntaxNode};
|
|
||||||
use crate::util::Static;
|
use crate::util::Static;
|
||||||
use crate::World;
|
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use typst_macros::func;
|
pub use typst_macros::func;
|
||||||
@ -276,9 +274,9 @@ impl Func {
|
|||||||
// Determine the route inside the closure.
|
// Determine the route inside the closure.
|
||||||
let fresh = Route::new(closure.file);
|
let fresh = Route::new(closure.file);
|
||||||
let route = if vm.file.is_none() { fresh.track() } else { vm.route };
|
let route = if vm.file.is_none() { fresh.track() } else { vm.route };
|
||||||
|
crate::eval::call_closure(
|
||||||
Closure::call(
|
|
||||||
self,
|
self,
|
||||||
|
closure,
|
||||||
vm.world(),
|
vm.world(),
|
||||||
route,
|
route,
|
||||||
vm.vt.introspector,
|
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 {
|
fn repr(&self) -> EcoString {
|
||||||
match self.name() {
|
match self.name() {
|
||||||
Some(name) => name.into(),
|
Some(name) => name.into(),
|
||||||
@ -496,7 +494,7 @@ pub struct ParamInfo {
|
|||||||
|
|
||||||
/// A user-defined closure.
|
/// A user-defined closure.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub(super) struct Closure {
|
pub struct Closure {
|
||||||
/// The closure's syntax node. Must be castable to `ast::Closure`.
|
/// The closure's syntax node. Must be castable to `ast::Closure`.
|
||||||
pub node: SyntaxNode,
|
pub node: SyntaxNode,
|
||||||
/// The source file where the closure was defined.
|
/// The source file where the closure was defined.
|
||||||
@ -516,116 +514,6 @@ impl Closure {
|
|||||||
.name()
|
.name()
|
||||||
.map(|ident| ident.as_str())
|
.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 {
|
impl From<Closure> for Func {
|
||||||
@ -638,233 +526,3 @@ cast! {
|
|||||||
Closure,
|
Closure,
|
||||||
self => Value::Func(self.into()),
|
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 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.
|
/// A whole number.
|
||||||
///
|
///
|
@ -1,8 +1,6 @@
|
|||||||
use std::fmt::Debug;
|
|
||||||
|
|
||||||
use ecow::{eco_format, EcoString};
|
use ecow::{eco_format, EcoString};
|
||||||
|
|
||||||
use crate::eval::{func, scope, ty, Repr};
|
use crate::foundations::{func, scope, ty, Repr};
|
||||||
use crate::util::PicoStr;
|
use crate::util::PicoStr;
|
||||||
|
|
||||||
/// A label for an element.
|
/// A label for an element.
|
@ -1,19 +1,9 @@
|
|||||||
//! Handles special built-in methods on values.
|
//! Handles special built-in methods on values.
|
||||||
|
|
||||||
use crate::diag::{At, SourceResult};
|
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;
|
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.
|
/// List the available methods for a type and whether they take arguments.
|
||||||
pub fn mutable_methods_on(ty: Type) -> &'static [(&'static str, bool)] {
|
pub fn mutable_methods_on(ty: Type) -> &'static [(&'static str, bool)] {
|
||||||
if ty == Type::of::<Array>() {
|
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.
|
/// Call a mutating method on a value.
|
||||||
pub fn call_mut(
|
pub(crate) fn call_method_mut(
|
||||||
value: &mut Value,
|
value: &mut Value,
|
||||||
method: &str,
|
method: &str,
|
||||||
mut args: Args,
|
mut args: Args,
|
||||||
@ -76,7 +76,7 @@ pub fn call_mut(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Call an accessor method on a value.
|
/// Call an accessor method on a value.
|
||||||
pub fn call_access<'a>(
|
pub(crate) fn call_method_access<'a>(
|
||||||
value: &'a mut Value,
|
value: &'a mut Value,
|
||||||
method: &str,
|
method: &str,
|
||||||
mut args: Args,
|
mut args: Args,
|
@ -1,16 +1,93 @@
|
|||||||
use typst::eval::{
|
//! Foundational types and functions.
|
||||||
Datetime, Duration, EvalMode, Module, Never, NoneValue, Plugin, Regex, Repr, Version,
|
|
||||||
|
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) {
|
pub(super) fn define(global: &mut Scope) {
|
||||||
global.category("foundations");
|
global.category(FOUNDATIONS);
|
||||||
global.define_type::<bool>();
|
global.define_type::<bool>();
|
||||||
global.define_type::<i64>();
|
global.define_type::<i64>();
|
||||||
global.define_type::<f64>();
|
global.define_type::<f64>();
|
||||||
global.define_type::<Str>();
|
global.define_type::<Str>();
|
||||||
|
global.define_type::<Label>();
|
||||||
global.define_type::<Bytes>();
|
global.define_type::<Bytes>();
|
||||||
global.define_type::<Content>();
|
global.define_type::<Content>();
|
||||||
global.define_type::<Array>();
|
global.define_type::<Array>();
|
||||||
@ -20,38 +97,18 @@ pub(super) fn define(global: &mut Scope) {
|
|||||||
global.define_type::<Type>();
|
global.define_type::<Type>();
|
||||||
global.define_type::<Module>();
|
global.define_type::<Module>();
|
||||||
global.define_type::<Regex>();
|
global.define_type::<Regex>();
|
||||||
|
global.define_type::<Selector>();
|
||||||
global.define_type::<Datetime>();
|
global.define_type::<Datetime>();
|
||||||
global.define_type::<Duration>();
|
global.define_type::<Duration>();
|
||||||
global.define_type::<Version>();
|
global.define_type::<Version>();
|
||||||
global.define_type::<Plugin>();
|
global.define_type::<Plugin>();
|
||||||
global.define_func::<repr>();
|
global.define_func::<repr::repr>();
|
||||||
global.define_func::<panic>();
|
global.define_func::<panic>();
|
||||||
global.define_func::<assert>();
|
global.define_func::<assert>();
|
||||||
global.define_func::<eval>();
|
global.define_func::<eval>();
|
||||||
}
|
global.define_func::<style>();
|
||||||
|
global.define_module(calc::module());
|
||||||
/// Returns the string representation of a value.
|
global.define_module(sys::module());
|
||||||
///
|
|
||||||
/// 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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fails with an error.
|
/// Fails with an error.
|
||||||
@ -232,5 +289,5 @@ pub fn eval(
|
|||||||
for (key, value) in dict {
|
for (key, value) in dict {
|
||||||
scope.define(key, value);
|
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 ecow::{eco_format, EcoString};
|
||||||
|
|
||||||
use crate::diag::StrResult;
|
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.
|
/// 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 {
|
fn repr(&self) -> EcoString {
|
||||||
eco_format!("<module {}>", self.name())
|
eco_format!("<module {}>", self.name())
|
||||||
}
|
}
|
@ -1,10 +1,12 @@
|
|||||||
use ecow::EcoString;
|
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
|
||||||
|
use ecow::EcoString;
|
||||||
use serde::{Serialize, Serializer};
|
use serde::{Serialize, Serializer};
|
||||||
|
|
||||||
use crate::diag::StrResult;
|
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.
|
/// A value that indicates the absence of any other value.
|
||||||
///
|
///
|
@ -1,12 +1,13 @@
|
|||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use ecow::{eco_format, EcoString};
|
use ecow::{eco_format, EcoString};
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use wasmi::{AsContext, AsContextMut, Caller, Engine, Linker, Module};
|
use wasmi::{AsContext, AsContextMut, Caller, Engine, Linker, Module};
|
||||||
|
|
||||||
use crate::diag::{bail, At, SourceResult, StrResult};
|
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::syntax::Spanned;
|
||||||
use crate::World;
|
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 {
|
fn repr(&self) -> EcoString {
|
||||||
"plugin(..)".into()
|
"plugin(..)".into()
|
||||||
}
|
}
|
@ -1,7 +1,36 @@
|
|||||||
|
//! Debug representation of values.
|
||||||
|
|
||||||
use ecow::{eco_format, EcoString};
|
use ecow::{eco_format, EcoString};
|
||||||
|
|
||||||
|
use crate::foundations::{func, Str, Value};
|
||||||
|
|
||||||
|
/// The Unicode minus sign.
|
||||||
pub const MINUS_SIGN: &str = "\u{2212}";
|
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.
|
/// A trait that defines the `repr` of a Typst value.
|
||||||
pub trait Repr {
|
pub trait Repr {
|
||||||
/// Return the debug representation of the value.
|
/// Return the debug representation of the value.
|
@ -5,10 +5,15 @@ use ecow::{eco_format, EcoString};
|
|||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
|
||||||
use crate::diag::{bail, HintedStrResult, HintedString, StrResult};
|
use crate::diag::{bail, HintedStrResult, HintedString, StrResult};
|
||||||
use crate::eval::{
|
use crate::foundations::{
|
||||||
Func, IntoValue, Library, Module, NativeFunc, NativeFuncData, NativeType, Type, Value,
|
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.
|
/// A stack of scopes.
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
@ -97,7 +102,7 @@ fn unknown_variable(var: &str) -> HintedString {
|
|||||||
pub struct Scope {
|
pub struct Scope {
|
||||||
map: IndexMap<EcoString, Slot>,
|
map: IndexMap<EcoString, Slot>,
|
||||||
deduplicate: bool,
|
deduplicate: bool,
|
||||||
category: Option<&'static str>,
|
category: Option<Category>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scope {
|
impl Scope {
|
||||||
@ -112,8 +117,8 @@ impl Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Enter a new category.
|
/// Enter a new category.
|
||||||
pub fn category(&mut self, name: &'static str) {
|
pub fn category(&mut self, category: Category) {
|
||||||
self.category = Some(name);
|
self.category = Some(category);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reset the category.
|
/// Reset the category.
|
||||||
@ -185,7 +190,7 @@ impl Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the category of a definition.
|
/// 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
|
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.
|
/// A slot where a value is stored.
|
||||||
#[derive(Clone, Hash)]
|
#[derive(Clone, Hash)]
|
||||||
struct Slot {
|
struct Slot {
|
||||||
@ -223,7 +237,7 @@ struct Slot {
|
|||||||
/// The kind of slot, determines how the value can be accessed.
|
/// The kind of slot, determines how the value can be accessed.
|
||||||
kind: Kind,
|
kind: Kind,
|
||||||
/// The category of the slot.
|
/// The category of the slot.
|
||||||
category: Option<&'static str>,
|
category: Option<Category>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The different kinds of slots.
|
/// The different kinds of slots.
|
||||||
@ -237,7 +251,7 @@ enum Kind {
|
|||||||
|
|
||||||
impl Slot {
|
impl Slot {
|
||||||
/// Create a new 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 }
|
Self { value, kind, category }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,11 +274,42 @@ impl Slot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Defines the associated scope of a Rust type.
|
/// A group of related definitions.
|
||||||
pub trait NativeScope {
|
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
/// The constructor function for the type, if any.
|
pub struct Category(Static<CategoryData>);
|
||||||
fn constructor() -> Option<&'static NativeFuncData>;
|
|
||||||
|
|
||||||
/// Get the associated scope for the type.
|
impl Category {
|
||||||
fn scope() -> Scope;
|
/// 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::any::{Any, TypeId};
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ecow::{eco_format, EcoString, EcoVec};
|
use ecow::{eco_format, EcoString, EcoVec};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::diag::{bail, StrResult};
|
use crate::diag::{bail, StrResult};
|
||||||
use crate::eval::{
|
use crate::foundations::{
|
||||||
cast, func, item, repr, scope, ty, CastInfo, Dict, FromValue, Func, Reflect, Regex,
|
cast, func, repr, scope, ty, CastInfo, Content, Dict, Element, FromValue, Func,
|
||||||
Repr, Str, Symbol, Type, Value,
|
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`]
|
/// 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();
|
let mut fields = ::smallvec::SmallVec::new();
|
||||||
$(
|
$(
|
||||||
fields.push((
|
fields.push((
|
||||||
<$ty as $crate::model::ElementFields>::Fields::$field as u8,
|
<$ty as $crate::foundations::ElementFields>::Fields::$field as u8,
|
||||||
$crate::eval::IntoValue::into_value($value),
|
$crate::foundations::IntoValue::into_value($value),
|
||||||
));
|
));
|
||||||
)*
|
)*
|
||||||
$crate::model::Selector::Elem(
|
$crate::foundations::Selector::Elem(
|
||||||
<$ty as $crate::model::NativeElement>::elem(),
|
<$ty as $crate::foundations::NativeElement>::elem(),
|
||||||
Some(fields),
|
Some(fields),
|
||||||
)
|
)
|
||||||
}};
|
}};
|
||||||
@ -138,10 +139,9 @@ impl Selector {
|
|||||||
.all(|(id, value)| target.get(*id).as_ref() == Some(value))
|
.all(|(id, value)| target.get(*id).as_ref() == Some(value))
|
||||||
}
|
}
|
||||||
Self::Label(label) => target.label() == Some(*label),
|
Self::Label(label) => target.label() == Some(*label),
|
||||||
Self::Regex(regex) => {
|
Self::Regex(regex) => target
|
||||||
target.func() == item!(text_elem)
|
.to::<TextElem>()
|
||||||
&& item!(text_str)(target).map_or(false, |text| regex.is_match(text))
|
.map_or(false, |elem| regex.is_match(elem.text())),
|
||||||
}
|
|
||||||
Self::Can(cap) => target.func().can_type_id(*cap),
|
Self::Can(cap) => target.func().can_type_id(*cap),
|
||||||
Self::Or(selectors) => selectors.iter().any(move |sel| sel.matches(target)),
|
Self::Or(selectors) => selectors.iter().any(move |sel| sel.matches(target)),
|
||||||
Self::And(selectors) => selectors.iter().all(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 unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use crate::diag::{bail, At, SourceResult, StrResult};
|
use crate::diag::{bail, At, SourceResult, StrResult};
|
||||||
use crate::eval::{
|
use crate::eval::Vm;
|
||||||
cast, dict, func, repr, scope, ty, Args, Array, Bytes, Dict, Func, IntoValue, Repr,
|
use crate::foundations::{
|
||||||
Type, Value, Version, Vm,
|
cast, dict, func, repr, scope, ty, Args, Array, Bytes, Dict, Func, IntoValue, Label,
|
||||||
|
Repr, Type, Value, Version,
|
||||||
};
|
};
|
||||||
use crate::geom::Align;
|
use crate::layout::Align;
|
||||||
use crate::model::Label;
|
|
||||||
use crate::syntax::{Span, Spanned};
|
use crate::syntax::{Span, Spanned};
|
||||||
|
|
||||||
/// Create a new [`Str`] from a format string.
|
/// Create a new [`Str`] from a format string.
|
||||||
@ -21,7 +21,7 @@ use crate::syntax::{Span, Spanned};
|
|||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
macro_rules! __format_str {
|
macro_rules! __format_str {
|
||||||
($($tts:tt)*) => {{
|
($($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::borrow::Cow;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::iter;
|
use std::{iter, mem, ptr};
|
||||||
use std::mem;
|
|
||||||
use std::ptr;
|
|
||||||
|
|
||||||
use comemo::Prehashed;
|
use comemo::Prehashed;
|
||||||
use ecow::{eco_vec, EcoString, EcoVec};
|
use ecow::{eco_vec, EcoString, EcoVec};
|
||||||
@ -12,9 +10,58 @@ use once_cell::sync::Lazy;
|
|||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::diag::{SourceResult, Trace, Tracepoint};
|
use crate::diag::{SourceResult, Trace, Tracepoint};
|
||||||
use crate::eval::{cast, ty, Args, Func, Repr, Value, Vm};
|
use crate::eval::Vm;
|
||||||
use crate::model::{Content, Element, NativeElement, Selector, Vt};
|
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::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.
|
/// A list of style properties.
|
||||||
#[ty]
|
#[ty]
|
||||||
@ -83,6 +130,16 @@ impl Styles {
|
|||||||
Style::Recipe(recipe) => recipe.is_of(elem).then_some(Some(recipe.span)),
|
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 {
|
impl From<Style> for Styles {
|
||||||
@ -279,7 +336,7 @@ pub struct Recipe {
|
|||||||
/// Determines whether the recipe applies to an element.
|
/// Determines whether the recipe applies to an element.
|
||||||
pub selector: Option<Selector>,
|
pub selector: Option<Selector>,
|
||||||
/// The transformation to perform on the match.
|
/// The transformation to perform on the match.
|
||||||
pub transform: Transform,
|
pub transform: Transformation,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Recipe {
|
impl Recipe {
|
||||||
@ -301,8 +358,8 @@ impl Recipe {
|
|||||||
/// Apply the recipe to the given content.
|
/// Apply the recipe to the given content.
|
||||||
pub fn apply_vm(&self, vm: &mut Vm, content: Content) -> SourceResult<Content> {
|
pub fn apply_vm(&self, vm: &mut Vm, content: Content) -> SourceResult<Content> {
|
||||||
match &self.transform {
|
match &self.transform {
|
||||||
Transform::Content(content) => Ok(content.clone()),
|
Transformation::Content(content) => Ok(content.clone()),
|
||||||
Transform::Func(func) => {
|
Transformation::Func(func) => {
|
||||||
let args = Args::new(self.span, [Value::Content(content.clone())]);
|
let args = Args::new(self.span, [Value::Content(content.clone())]);
|
||||||
let mut result = func.call_vm(vm, args);
|
let mut result = func.call_vm(vm, args);
|
||||||
// For selector-less show rules, a tracepoint makes no sense.
|
// For selector-less show rules, a tracepoint makes no sense.
|
||||||
@ -312,15 +369,15 @@ impl Recipe {
|
|||||||
}
|
}
|
||||||
Ok(result?.display())
|
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.
|
/// Apply the recipe to the given content.
|
||||||
pub fn apply_vt(&self, vt: &mut Vt, content: Content) -> SourceResult<Content> {
|
pub fn apply_vt(&self, vt: &mut Vt, content: Content) -> SourceResult<Content> {
|
||||||
match &self.transform {
|
match &self.transform {
|
||||||
Transform::Content(content) => Ok(content.clone()),
|
Transformation::Content(content) => Ok(content.clone()),
|
||||||
Transform::Func(func) => {
|
Transformation::Func(func) => {
|
||||||
let mut result = func.call_vt(vt, [Value::Content(content.clone())]);
|
let mut result = func.call_vt(vt, [Value::Content(content.clone())]);
|
||||||
if self.selector.is_some() {
|
if self.selector.is_some() {
|
||||||
let point = || Tracepoint::Show(content.func().name().into());
|
let point = || Tracepoint::Show(content.func().name().into());
|
||||||
@ -328,7 +385,7 @@ impl Recipe {
|
|||||||
}
|
}
|
||||||
Ok(result?.display())
|
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.
|
/// A show rule transformation that can be applied to a match.
|
||||||
#[derive(Clone, PartialEq, Hash)]
|
#[derive(Clone, PartialEq, Hash)]
|
||||||
pub enum Transform {
|
pub enum Transformation {
|
||||||
/// Replacement content.
|
/// Replacement content.
|
||||||
Content(Content),
|
Content(Content),
|
||||||
/// A function to apply to the match.
|
/// A function to apply to the match.
|
||||||
@ -355,7 +412,7 @@ pub enum Transform {
|
|||||||
Style(Styles),
|
Style(Styles),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Transform {
|
impl Debug for Transformation {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Content(content) => content.fmt(f),
|
Self::Content(content) => content.fmt(f),
|
||||||
@ -366,7 +423,7 @@ impl Debug for Transform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cast! {
|
cast! {
|
||||||
Transform,
|
Transformation,
|
||||||
content: Content => Self::Content(content),
|
content: Content => Self::Content(content),
|
||||||
func: Func => Self::Func(func),
|
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