From 5ae81971f299688b05d77af208d7bb44ffce5e2d Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 23 Nov 2022 15:58:59 +0100 Subject: Introduce `Library` --- cli/src/main.rs | 28 +++++----- library/src/lib.rs | 15 ++++-- src/lib.rs | 33 ++++-------- src/model/eval.rs | 7 ++- src/model/items.rs | 130 ----------------------------------------------- src/model/library.rs | 141 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/model/mod.rs | 4 +- src/model/vm.rs | 4 +- tests/src/benches.rs | 27 +++++----- tests/src/tests.rs | 46 ++++++++--------- 10 files changed, 219 insertions(+), 216 deletions(-) delete mode 100644 src/model/items.rs create mode 100644 src/model/library.rs diff --git a/cli/src/main.rs b/cli/src/main.rs index f7fd5903..3db491bb 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -19,9 +19,10 @@ use siphasher::sip128::{Hasher128, SipHasher}; use termcolor::{ColorChoice, StandardStream, WriteColor}; use typst::diag::{FileError, FileResult, SourceError, StrResult}; use typst::font::{Font, FontBook, FontInfo, FontVariant}; +use typst::model::Library; use typst::syntax::{Source, SourceId}; use typst::util::{Buffer, PathExt}; -use typst::{Config, World}; +use typst::World; use walkdir::WalkDir; type CodespanResult = Result; @@ -178,15 +179,8 @@ fn typeset(command: TypesetCommand) -> StrResult<()> { PathBuf::new() }; - let config = Config { - root, - scope: typst_library::scope(), - styles: typst_library::styles(), - items: typst_library::items(), - }; - // Create the world that serves sources, fonts and files. - let mut world = SystemWorld::new(config); + let mut world = SystemWorld::new(root); // Typeset. typeset_once(&mut world, &command)?; @@ -371,7 +365,8 @@ fn fonts(command: FontsCommand) -> StrResult<()> { /// A world that provides access to the operating system. struct SystemWorld { - config: Prehashed, + root: PathBuf, + library: Prehashed, book: Prehashed, fonts: Vec, hashes: RefCell>>, @@ -394,12 +389,13 @@ struct PathSlot { } impl SystemWorld { - fn new(config: Config) -> Self { + fn new(root: PathBuf) -> Self { let mut searcher = FontSearcher::new(); searcher.search_system(); Self { - config: Prehashed::new(config), + root, + library: Prehashed::new(typst_library::new()), book: Prehashed::new(searcher.book), fonts: searcher.fonts, hashes: RefCell::default(), @@ -410,8 +406,12 @@ impl SystemWorld { } impl World for SystemWorld { - fn config(&self) -> &Prehashed { - &self.config + fn root(&self) -> &Path { + &self.root + } + + fn library(&self) -> &Prehashed { + &self.library } fn book(&self) -> &Prehashed { diff --git a/library/src/lib.rs b/library/src/lib.rs index e5e60968..7fcad183 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -10,12 +10,17 @@ pub mod structure; pub mod text; use typst::geom::{Align, Color, Dir, GenAlign}; -use typst::model::{LangItems, Node, NodeId, Scope, StyleMap}; +use typst::model::{LangItems, Library, Node, NodeId, Scope, StyleMap}; use self::layout::LayoutRoot; -/// Construct the standard library scope. -pub fn scope() -> Scope { +/// Construct the standard library. +pub fn new() -> Library { + Library { scope: scope(), styles: styles(), items: items() } +} + +/// Construct the standard scope. +fn scope() -> Scope { let mut std = Scope::new(); // Text. @@ -147,12 +152,12 @@ pub fn scope() -> Scope { } /// Construct the standard style map. -pub fn styles() -> StyleMap { +fn styles() -> StyleMap { StyleMap::new() } /// Construct the standard lang item mapping. -pub fn items() -> LangItems { +fn items() -> LangItems { LangItems { layout: |content, world, styles| content.layout_root(world, styles), em: |styles| styles.get(text::TextNode::SIZE), diff --git a/src/lib.rs b/src/lib.rs index 319c13ab..75200144 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,14 +42,14 @@ pub mod frame; pub mod image; pub mod syntax; -use std::path::{Path, PathBuf}; +use std::path::Path; use comemo::{Prehashed, Track}; use crate::diag::{FileResult, SourceResult}; use crate::font::{Font, FontBook}; use crate::frame::Frame; -use crate::model::{LangItems, Route, Scope, StyleChain, StyleMap}; +use crate::model::{Library, Route, StyleChain}; use crate::syntax::{Source, SourceId}; use crate::util::Buffer; @@ -62,24 +62,24 @@ pub fn typeset( world: &(dyn World + 'static), source: &Source, ) -> SourceResult> { - // Set up the language items. - let config = world.config(); - crate::model::set_lang_items(config.items); - // Evaluate the source file into a module. let route = Route::default(); let module = model::eval(world.track(), route.track(), source)?; // Layout the module's contents. - let styles = StyleChain::with_root(&config.styles); - item!(layout)(&module.content, world.track(), styles) + let library = world.library(); + let styles = StyleChain::with_root(&library.styles); + (library.items.layout)(&module.content, world.track(), styles) } /// The environment in which typesetting occurs. #[comemo::track] pub trait World { - /// Access the global configuration. - fn config(&self) -> &Prehashed; + /// The compilation root. + fn root(&self) -> &Path; + + /// The standard library. + fn library(&self) -> &Prehashed; /// Metadata about all known fonts. fn book(&self) -> &Prehashed; @@ -96,16 +96,3 @@ pub trait World { /// Access a source file by id. fn source(&self, id: SourceId) -> &Source; } - -/// The global configuration for typesetting. -#[derive(Debug, Clone, Hash)] -pub struct Config { - /// The compilation root, relative to which absolute paths are. - pub root: PathBuf, - /// The scope containing definitions that are available everywhere. - pub scope: Scope, - /// The default properties for page size, font selection and so on. - pub styles: StyleMap, - /// Defines which standard library items fulfill which syntactical roles. - pub items: LangItems, -} diff --git a/src/model/eval.rs b/src/model/eval.rs index 7f2cea63..fb1bd121 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -35,10 +35,13 @@ pub fn eval( panic!("Tried to cyclicly evaluate {}", path); } + // Hook up the lang items. + let library = world.library(); + super::set_lang_items(library.items.clone()); + // Evaluate the module. let route = unsafe { Route::insert(route, id) }; - let std = &world.config().scope; - let scopes = Scopes::new(Some(std)); + let scopes = Scopes::new(Some(&library.scope)); let mut vm = Vm::new(world, route.track(), id, scopes); let result = source.ast()?.eval(&mut vm); diff --git a/src/model/items.rs b/src/model/items.rs deleted file mode 100644 index 50ea8b60..00000000 --- a/src/model/items.rs +++ /dev/null @@ -1,130 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; -use std::hash::{Hash, Hasher}; -use std::num::NonZeroUsize; - -use comemo::Tracked; -use once_cell::sync::OnceCell; - -use super::{Content, NodeId, StyleChain}; -use crate::diag::SourceResult; -use crate::frame::Frame; -use crate::geom::{Abs, Dir}; -use crate::util::{hash128, EcoString}; -use crate::World; - -/// Global storage for lang items. -#[doc(hidden)] -pub static LANG_ITEMS: OnceCell = OnceCell::new(); - -/// Set the lang items. This is a hack :( -/// -/// Passing the lang items everywhere they are needed (especially the text node -/// 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. -pub(crate) fn set_lang_items(items: LangItems) { - if LANG_ITEMS.set(items).is_err() { - let first = hash128(LANG_ITEMS.get().unwrap()); - let second = hash128(&items); - assert_eq!(first, second, "set differing lang items"); - } -} - -/// Access a lang item. -macro_rules! item { - ($name:ident) => { - $crate::model::LANG_ITEMS.get().unwrap().$name - }; -} - -/// Definition of certain standard library items the language is aware of. -#[derive(Copy, Clone)] -pub struct LangItems { - /// The root layout function. - pub layout: fn( - content: &Content, - world: Tracked, - styles: StyleChain, - ) -> SourceResult>, - /// 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(justify: bool) -> Content, - /// Plain text without markup. - pub text: fn(text: EcoString) -> Content, - /// The id of the text node. - pub text_id: NodeId, - /// Get the string if this is a text node. - pub text_str: fn(&Content) -> Option<&str>, - /// 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, block: bool) -> Content, - /// A hyperlink: `https://typst.org`. - pub link: fn(url: EcoString) -> Content, - /// A reference: `@target`. - pub ref_: fn(target: EcoString) -> Content, - /// A section heading: `= Introduction`. - pub heading: fn(level: NonZeroUsize, body: Content) -> Content, - /// An item in an unordered list: `- ...`. - pub list_item: fn(body: Content) -> Content, - /// An item in an enumeration (ordered list): `+ ...` or `1. ...`. - pub enum_item: fn(number: Option, body: Content) -> Content, - /// An item in a description list: `/ Term: Details`. - pub desc_item: fn(term: Content, body: Content) -> Content, - /// A mathematical formula: `$x$`, `$ x^2 $`. - pub math: fn(children: Vec, display: bool) -> Content, - /// An atom in a formula: `x`, `+`, `12`. - pub math_atom: fn(atom: EcoString) -> Content, - /// A base with optional sub- and superscripts in a formula: `a_1^2`. - pub math_script: - fn(base: Content, sub: Option, sup: Option) -> Content, - /// A fraction in a formula: `x/2`. - pub math_frac: fn(num: Content, denom: Content) -> Content, - /// An alignment indicator in a formula: `&`, `&&`. - pub math_align: fn(count: usize) -> Content, -} - -impl Debug for LangItems { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad("LangItems { .. }") - } -} - -impl Hash for LangItems { - fn hash(&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.smart_quote.hash(state); - self.parbreak.hash(state); - self.strong.hash(state); - self.emph.hash(state); - self.raw.hash(state); - self.link.hash(state); - self.ref_.hash(state); - self.heading.hash(state); - self.list_item.hash(state); - self.enum_item.hash(state); - self.desc_item.hash(state); - self.math.hash(state); - self.math_atom.hash(state); - self.math_script.hash(state); - self.math_frac.hash(state); - self.math_align.hash(state); - } -} diff --git a/src/model/library.rs b/src/model/library.rs new file mode 100644 index 00000000..8433e514 --- /dev/null +++ b/src/model/library.rs @@ -0,0 +1,141 @@ +use std::fmt::{self, Debug, Formatter}; +use std::hash::{Hash, Hasher}; +use std::num::NonZeroUsize; + +use comemo::Tracked; +use once_cell::sync::OnceCell; + +use super::{Content, NodeId, Scope, StyleChain, StyleMap}; +use crate::diag::SourceResult; +use crate::frame::Frame; +use crate::geom::{Abs, Dir}; +use crate::util::{hash128, EcoString}; +use crate::World; + +/// A Typst standard library. +#[derive(Debug, Clone, Hash)] +pub struct Library { + /// The scope containing definitions that are available everywhere. + pub scope: Scope, + /// The default properties for page size, font selection and so on. + pub styles: StyleMap, + /// Defines which standard library items fulfill which syntactical roles. + pub items: LangItems, +} + +/// Definition of certain standard library items the language is aware of. +#[derive(Clone)] +pub struct LangItems { + /// The root layout function. + pub layout: fn( + content: &Content, + world: Tracked, + styles: StyleChain, + ) -> SourceResult>, + /// 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(justify: bool) -> Content, + /// Plain text without markup. + pub text: fn(text: EcoString) -> Content, + /// The id of the text node. + pub text_id: NodeId, + /// Get the string if this is a text node. + pub text_str: fn(&Content) -> Option<&str>, + /// 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, block: bool) -> Content, + /// A hyperlink: `https://typst.org`. + pub link: fn(url: EcoString) -> Content, + /// A reference: `@target`. + pub ref_: fn(target: EcoString) -> Content, + /// A section heading: `= Introduction`. + pub heading: fn(level: NonZeroUsize, body: Content) -> Content, + /// An item in an unordered list: `- ...`. + pub list_item: fn(body: Content) -> Content, + /// An item in an enumeration (ordered list): `+ ...` or `1. ...`. + pub enum_item: fn(number: Option, body: Content) -> Content, + /// An item in a description list: `/ Term: Details`. + pub desc_item: fn(term: Content, body: Content) -> Content, + /// A mathematical formula: `$x$`, `$ x^2 $`. + pub math: fn(children: Vec, display: bool) -> Content, + /// An atom in a formula: `x`, `+`, `12`. + pub math_atom: fn(atom: EcoString) -> Content, + /// A base with optional sub- and superscripts in a formula: `a_1^2`. + pub math_script: + fn(base: Content, sub: Option, sup: Option) -> Content, + /// A fraction in a formula: `x/2`. + pub math_frac: fn(num: Content, denom: Content) -> Content, + /// An alignment indicator in a formula: `&`, `&&`. + pub math_align: fn(count: usize) -> Content, +} + +impl Debug for LangItems { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad("LangItems { .. }") + } +} + +impl Hash for LangItems { + fn hash(&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.smart_quote.hash(state); + self.parbreak.hash(state); + self.strong.hash(state); + self.emph.hash(state); + self.raw.hash(state); + self.link.hash(state); + self.ref_.hash(state); + self.heading.hash(state); + self.list_item.hash(state); + self.enum_item.hash(state); + self.desc_item.hash(state); + self.math.hash(state); + self.math_atom.hash(state); + self.math_script.hash(state); + self.math_frac.hash(state); + self.math_align.hash(state); + } +} + +/// Global storage for lang items. +#[doc(hidden)] +pub static LANG_ITEMS: OnceCell = OnceCell::new(); + +/// Set the lang items. This is a hack :( +/// +/// Passing the lang items everywhere they are needed (especially the text node +/// 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. +pub(crate) 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_rules! item { + ($name:ident) => { + $crate::model::LANG_ITEMS.get().unwrap().$name + }; +} diff --git a/src/model/mod.rs b/src/model/mod.rs index 0f946a40..93e33d5c 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,7 +1,7 @@ //! Document and computation model. #[macro_use] -mod items; +mod library; #[macro_use] mod cast; #[macro_use] @@ -34,7 +34,7 @@ pub use self::content::*; pub use self::dict::*; pub use self::eval::*; pub use self::func::*; -pub use self::items::*; +pub use self::library::*; pub use self::scope::*; pub use self::str::*; pub use self::styles::*; diff --git a/src/model/vm.rs b/src/model/vm.rs index 28885f29..d13be29c 100644 --- a/src/model/vm.rs +++ b/src/model/vm.rs @@ -38,7 +38,7 @@ impl<'a> Vm<'a> { location, scopes, flow: None, - items: world.config().items, + items: world.library().items.clone(), } } @@ -47,7 +47,7 @@ impl<'a> Vm<'a> { pub fn locate(&self, path: &str) -> StrResult { if !self.location.is_detached() { if let Some(path) = path.strip_prefix('/') { - return Ok(self.world.config().root.join(path).normalize()); + return Ok(self.world.root().join(path).normalize()); } if let Some(dir) = self.world.source(self.location).path().parent() { diff --git a/tests/src/benches.rs b/tests/src/benches.rs index 29117a90..1e5550c5 100644 --- a/tests/src/benches.rs +++ b/tests/src/benches.rs @@ -1,12 +1,13 @@ -use std::path::{Path, PathBuf}; +use std::path::Path; use comemo::{Prehashed, Track, Tracked}; use iai::{black_box, main, Iai}; use typst::diag::{FileError, FileResult}; use typst::font::{Font, FontBook}; +use typst::model::Library; use typst::syntax::{Source, SourceId, TokenMode, Tokens}; use typst::util::Buffer; -use typst::{Config, World}; +use typst::World; use unscanny::Scanner; const TEXT: &str = include_str!("../typ/benches/bench.typ"); @@ -90,7 +91,7 @@ fn bench_render(iai: &mut Iai) { } struct BenchWorld { - config: Prehashed, + library: Prehashed, book: Prehashed, font: Font, source: Source, @@ -98,22 +99,14 @@ struct BenchWorld { impl BenchWorld { fn new() -> Self { - let config = Config { - root: PathBuf::new(), - scope: typst_library::scope(), - styles: typst_library::styles(), - items: typst_library::items(), - }; - let font = Font::new(FONT.into(), 0).unwrap(); let book = FontBook::from_fonts([&font]); - let source = Source::detached(TEXT); Self { - config: Prehashed::new(config), + library: Prehashed::new(typst_library::new()), book: Prehashed::new(book), font, - source, + source: Source::detached(TEXT), } } @@ -123,8 +116,12 @@ impl BenchWorld { } impl World for BenchWorld { - fn config(&self) -> &Prehashed { - &self.config + fn root(&self) -> &Path { + Path::new("") + } + + fn library(&self) -> &Prehashed { + &self.library } fn book(&self) -> &Prehashed { diff --git a/tests/src/tests.rs b/tests/src/tests.rs index 89c83857..2c0a1e71 100644 --- a/tests/src/tests.rs +++ b/tests/src/tests.rs @@ -15,10 +15,10 @@ use typst::diag::{bail, FileError, FileResult}; use typst::font::{Font, FontBook}; use typst::frame::{Element, Frame}; use typst::geom::{Abs, RgbaColor, Sides}; -use typst::model::{Smart, Value}; +use typst::model::{Library, Smart, Value}; use typst::syntax::{Source, SourceId, SyntaxNode}; use typst::util::{Buffer, PathExt}; -use typst::{Config, World}; +use typst::World; use typst_library::layout::PageNode; use typst_library::text::{TextNode, TextSize}; use unscanny::Scanner; @@ -144,21 +144,22 @@ impl Args { } } -fn config() -> Config { +fn library() -> Library { + let mut lib = typst_library::new(); + // Set page width to 120pt with 10pt margins, so that the inner page is // exactly 100pt wide. Page height is unbounded and font size is 10pt so // that it multiplies to nice round numbers. - let mut styles = typst_library::styles(); - styles.set(PageNode::WIDTH, Smart::Custom(Abs::pt(120.0).into())); - styles.set(PageNode::HEIGHT, Smart::Auto); - styles.set(PageNode::MARGIN, Sides::splat(Some(Smart::Custom(Abs::pt(10.0).into())))); - styles.set(TextNode::SIZE, TextSize(Abs::pt(10.0).into())); + lib.styles.set(PageNode::WIDTH, Smart::Custom(Abs::pt(120.0).into())); + lib.styles.set(PageNode::HEIGHT, Smart::Auto); + lib.styles + .set(PageNode::MARGIN, Sides::splat(Some(Smart::Custom(Abs::pt(10.0).into())))); + lib.styles.set(TextNode::SIZE, TextSize(Abs::pt(10.0).into())); // Hook up helpers into the global scope. - let mut scope = typst_library::scope(); - scope.define("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF)); - scope.define("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF)); - scope.def_fn("test", move |_, args| { + lib.scope.define("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF)); + lib.scope.define("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF)); + lib.scope.def_fn("test", move |_, args| { let lhs = args.expect::("left-hand side")?; let rhs = args.expect::("right-hand side")?; if lhs != rhs { @@ -166,7 +167,7 @@ fn config() -> Config { } Ok(Value::None) }); - scope.def_fn("print", move |_, args| { + lib.scope.def_fn("print", move |_, args| { print!("> "); for (i, value) in args.all::()?.into_iter().enumerate() { if i > 0 { @@ -178,18 +179,13 @@ fn config() -> Config { Ok(Value::None) }); - Config { - root: PathBuf::new(), - scope, - styles, - items: typst_library::items(), - } + lib } /// A world that provides access to the tests environment. struct TestWorld { print: PrintConfig, - config: Prehashed, + library: Prehashed, book: Prehashed, fonts: Vec, paths: RefCell>, @@ -219,7 +215,7 @@ impl TestWorld { Self { print, - config: Prehashed::new(config()), + library: Prehashed::new(library()), book: Prehashed::new(FontBook::from_fonts(&fonts)), fonts, paths: RefCell::default(), @@ -229,8 +225,12 @@ impl TestWorld { } impl World for TestWorld { - fn config(&self) -> &Prehashed { - &self.config + fn root(&self) -> &Path { + Path::new("") + } + + fn library(&self) -> &Prehashed { + &self.library } fn book(&self) -> &Prehashed { -- cgit v1.2.3