mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
A New World
This commit is contained in:
parent
e29f55bb29
commit
757a701c1a
17
Cargo.lock
generated
17
Cargo.lock
generated
@ -189,6 +189,15 @@ version = "1.8.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
|
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "elsa"
|
||||||
|
version = "1.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b4b5d23ed6b6948d68240aafa4ac98e568c9a020efd9d4201a6288bc3006e09"
|
||||||
|
dependencies = [
|
||||||
|
"stable_deref_trait",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fancy-regex"
|
name = "fancy-regex"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
@ -710,6 +719,12 @@ version = "1.9.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
|
checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stable_deref_trait"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "static_assertions"
|
name = "static_assertions"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@ -838,6 +853,7 @@ dependencies = [
|
|||||||
"codespan-reporting",
|
"codespan-reporting",
|
||||||
"csv",
|
"csv",
|
||||||
"dirs",
|
"dirs",
|
||||||
|
"elsa",
|
||||||
"flate2",
|
"flate2",
|
||||||
"fxhash",
|
"fxhash",
|
||||||
"hypher",
|
"hypher",
|
||||||
@ -858,6 +874,7 @@ dependencies = [
|
|||||||
"rustybuzz",
|
"rustybuzz",
|
||||||
"same-file",
|
"same-file",
|
||||||
"serde",
|
"serde",
|
||||||
|
"siphasher",
|
||||||
"subsetter",
|
"subsetter",
|
||||||
"svg2pdf",
|
"svg2pdf",
|
||||||
"syntect",
|
"syntect",
|
||||||
|
25
Cargo.toml
25
Cargo.toml
@ -5,9 +5,18 @@ authors = ["The Typst Project Developers"]
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["fs"]
|
default = ["tests"]
|
||||||
cli = ["fs", "pico-args", "codespan-reporting", "same-file"]
|
tests = ["same-file", "walkdir", "elsa", "siphasher"]
|
||||||
fs = ["dirs", "memmap2", "walkdir", "same-file"]
|
cli = [
|
||||||
|
"pico-args",
|
||||||
|
"codespan-reporting",
|
||||||
|
"dirs",
|
||||||
|
"memmap2",
|
||||||
|
"same-file",
|
||||||
|
"walkdir",
|
||||||
|
"elsa",
|
||||||
|
"siphasher",
|
||||||
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Workspace
|
# Workspace
|
||||||
@ -57,15 +66,15 @@ resvg = { version = "0.22", default-features = false }
|
|||||||
roxmltree = "0.14"
|
roxmltree = "0.14"
|
||||||
flate2 = "1"
|
flate2 = "1"
|
||||||
|
|
||||||
# Command line interface
|
# Command line interface / tests
|
||||||
pico-args = { version = "0.4", optional = true }
|
pico-args = { version = "0.4", optional = true }
|
||||||
codespan-reporting = { version = "0.11", optional = true }
|
codespan-reporting = { version = "0.11", optional = true }
|
||||||
same-file = { version = "1", optional = true }
|
same-file = { version = "1", optional = true }
|
||||||
|
walkdir = { version = "2", optional = true }
|
||||||
# File system loading
|
elsa = { version = "1.7", optional = true }
|
||||||
dirs = { version = "4", optional = true }
|
dirs = { version = "4", optional = true }
|
||||||
memmap2 = { version = "0.5", optional = true }
|
memmap2 = { version = "0.5", optional = true }
|
||||||
walkdir = { version = "2", optional = true }
|
siphasher = { version = "0.3", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
iai = { git = "https://github.com/reknih/iai" }
|
iai = { git = "https://github.com/reknih/iai" }
|
||||||
@ -85,7 +94,7 @@ required-features = ["cli"]
|
|||||||
|
|
||||||
[[test]]
|
[[test]]
|
||||||
name = "typeset"
|
name = "typeset"
|
||||||
required-features = ["fs"]
|
required-features = ["tests"]
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
|
@ -1,26 +1,18 @@
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use iai::{black_box, main, Iai};
|
use iai::{black_box, main, Iai};
|
||||||
use unscanny::Scanner;
|
use unscanny::Scanner;
|
||||||
|
|
||||||
use typst::font::{Font, FontBook};
|
use typst::font::{Font, FontBook};
|
||||||
use typst::loading::{Buffer, FileHash, Loader};
|
|
||||||
use typst::parse::{TokenMode, Tokens};
|
use typst::parse::{TokenMode, Tokens};
|
||||||
use typst::source::SourceId;
|
use typst::source::{Source, SourceId};
|
||||||
use typst::{Config, Context};
|
use typst::util::Buffer;
|
||||||
|
use typst::{Config, World};
|
||||||
|
|
||||||
const SRC: &str = include_str!("bench.typ");
|
const TEXT: &str = include_str!("bench.typ");
|
||||||
const FONT: &[u8] = include_bytes!("../fonts/IBMPlexSans-Regular.ttf");
|
const FONT: &[u8] = include_bytes!("../fonts/IBMPlexSans-Regular.ttf");
|
||||||
|
|
||||||
fn context() -> (Context, SourceId) {
|
|
||||||
let loader = BenchLoader::new();
|
|
||||||
let mut ctx = Context::new(Arc::new(loader), Config::default());
|
|
||||||
let id = ctx.sources.provide(Path::new("src.typ"), SRC.to_string());
|
|
||||||
(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
main!(
|
main!(
|
||||||
bench_decode,
|
bench_decode,
|
||||||
bench_scan,
|
bench_scan,
|
||||||
@ -38,7 +30,7 @@ fn bench_decode(iai: &mut Iai) {
|
|||||||
// We don't use chars().count() because that has a special
|
// We don't use chars().count() because that has a special
|
||||||
// superfast implementation.
|
// superfast implementation.
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
let mut chars = black_box(SRC).chars();
|
let mut chars = black_box(TEXT).chars();
|
||||||
while let Some(_) = chars.next() {
|
while let Some(_) = chars.next() {
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
@ -49,7 +41,7 @@ fn bench_decode(iai: &mut Iai) {
|
|||||||
fn bench_scan(iai: &mut Iai) {
|
fn bench_scan(iai: &mut Iai) {
|
||||||
iai.run(|| {
|
iai.run(|| {
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
let mut scanner = Scanner::new(black_box(SRC));
|
let mut scanner = Scanner::new(black_box(TEXT));
|
||||||
while let Some(_) = scanner.eat() {
|
while let Some(_) = scanner.eat() {
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
@ -58,21 +50,20 @@ fn bench_scan(iai: &mut Iai) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn bench_tokenize(iai: &mut Iai) {
|
fn bench_tokenize(iai: &mut Iai) {
|
||||||
iai.run(|| Tokens::new(black_box(SRC), black_box(TokenMode::Markup)).count());
|
iai.run(|| Tokens::new(black_box(TEXT), black_box(TokenMode::Markup)).count());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_parse(iai: &mut Iai) {
|
fn bench_parse(iai: &mut Iai) {
|
||||||
iai.run(|| typst::parse::parse(SRC));
|
iai.run(|| typst::parse::parse(TEXT));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_edit(iai: &mut Iai) {
|
fn bench_edit(iai: &mut Iai) {
|
||||||
let (mut ctx, id) = context();
|
let mut source = Source::detached(TEXT);
|
||||||
iai.run(|| black_box(ctx.sources.edit(id, 1168 .. 1171, "_Uhr_")));
|
iai.run(|| black_box(source.edit(1168 .. 1171, "_Uhr_")));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_highlight(iai: &mut Iai) {
|
fn bench_highlight(iai: &mut Iai) {
|
||||||
let (ctx, id) = context();
|
let source = Source::detached(TEXT);
|
||||||
let source = ctx.sources.get(id);
|
|
||||||
iai.run(|| {
|
iai.run(|| {
|
||||||
typst::syntax::highlight_node(
|
typst::syntax::highlight_node(
|
||||||
source.root(),
|
source.root(),
|
||||||
@ -83,36 +74,60 @@ fn bench_highlight(iai: &mut Iai) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn bench_eval(iai: &mut Iai) {
|
fn bench_eval(iai: &mut Iai) {
|
||||||
let (mut ctx, id) = context();
|
let world = BenchWorld::new();
|
||||||
iai.run(|| typst::eval::evaluate(&mut ctx, id, vec![]).unwrap());
|
let id = world.source.id();
|
||||||
|
iai.run(|| typst::eval::evaluate(&world, id, vec![]).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_layout(iai: &mut Iai) {
|
fn bench_layout(iai: &mut Iai) {
|
||||||
let (mut ctx, id) = context();
|
let world = BenchWorld::new();
|
||||||
let module = typst::eval::evaluate(&mut ctx, id, vec![]).unwrap();
|
let id = world.source.id();
|
||||||
iai.run(|| typst::model::layout(&mut ctx, &module.content));
|
let module = typst::eval::evaluate(&world, id, vec![]).unwrap();
|
||||||
|
iai.run(|| typst::model::layout(&world, &module.content));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_render(iai: &mut Iai) {
|
fn bench_render(iai: &mut Iai) {
|
||||||
let (mut ctx, id) = context();
|
let world = BenchWorld::new();
|
||||||
let frames = typst::typeset(&mut ctx, id).unwrap();
|
let id = world.source.id();
|
||||||
|
let frames = typst::typeset(&world, id).unwrap();
|
||||||
iai.run(|| typst::export::render(&frames[0], 1.0))
|
iai.run(|| typst::export::render(&frames[0], 1.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BenchLoader {
|
struct BenchWorld {
|
||||||
|
config: Config,
|
||||||
book: FontBook,
|
book: FontBook,
|
||||||
font: Font,
|
font: Font,
|
||||||
|
source: Source,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BenchLoader {
|
impl BenchWorld {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
let font = Font::new(FONT.into(), 0).unwrap();
|
let font = Font::new(FONT.into(), 0).unwrap();
|
||||||
let book = FontBook::from_fonts([&font]);
|
let book = FontBook::from_fonts([&font]);
|
||||||
Self { book, font }
|
let id = SourceId::from_raw(0);
|
||||||
|
let source = Source::new(id, Path::new("bench.typ"), TEXT.into());
|
||||||
|
Self {
|
||||||
|
config: Config::default(),
|
||||||
|
book,
|
||||||
|
font,
|
||||||
|
source,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Loader for BenchLoader {
|
impl World for BenchWorld {
|
||||||
|
fn config(&self) -> &Config {
|
||||||
|
&self.config
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve(&self, _: &Path) -> io::Result<SourceId> {
|
||||||
|
Err(io::ErrorKind::NotFound.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn source(&self, _: SourceId) -> &Source {
|
||||||
|
&self.source
|
||||||
|
}
|
||||||
|
|
||||||
fn book(&self) -> &FontBook {
|
fn book(&self) -> &FontBook {
|
||||||
&self.book
|
&self.book
|
||||||
}
|
}
|
||||||
@ -121,10 +136,6 @@ impl Loader for BenchLoader {
|
|||||||
Ok(self.font.clone())
|
Ok(self.font.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve(&self, _: &Path) -> io::Result<FileHash> {
|
|
||||||
Err(io::ErrorKind::NotFound.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn file(&self, _: &Path) -> io::Result<Buffer> {
|
fn file(&self, _: &Path) -> io::Result<Buffer> {
|
||||||
Err(io::ErrorKind::NotFound.into())
|
Err(io::ErrorKind::NotFound.into())
|
||||||
}
|
}
|
||||||
|
10
src/diag.rs
10
src/diag.rs
@ -5,7 +5,7 @@ use std::io;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::syntax::{Span, Spanned};
|
use crate::syntax::{Span, Spanned};
|
||||||
use crate::Context;
|
use crate::World;
|
||||||
|
|
||||||
/// Early-return with a [`TypError`].
|
/// Early-return with a [`TypError`].
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
@ -101,21 +101,21 @@ where
|
|||||||
/// Enrich a [`TypResult`] with a tracepoint.
|
/// Enrich a [`TypResult`] with a tracepoint.
|
||||||
pub trait Trace<T> {
|
pub trait Trace<T> {
|
||||||
/// Add the tracepoint to all errors that lie outside the `span`.
|
/// Add the tracepoint to all errors that lie outside the `span`.
|
||||||
fn trace<F>(self, ctx: &Context, make_point: F, span: Span) -> Self
|
fn trace<F>(self, world: &dyn World, make_point: F, span: Span) -> Self
|
||||||
where
|
where
|
||||||
F: Fn() -> Tracepoint;
|
F: Fn() -> Tracepoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Trace<T> for TypResult<T> {
|
impl<T> Trace<T> for TypResult<T> {
|
||||||
fn trace<F>(self, ctx: &Context, make_point: F, span: Span) -> Self
|
fn trace<F>(self, world: &dyn World, make_point: F, span: Span) -> Self
|
||||||
where
|
where
|
||||||
F: Fn() -> Tracepoint,
|
F: Fn() -> Tracepoint,
|
||||||
{
|
{
|
||||||
self.map_err(|mut errors| {
|
self.map_err(|mut errors| {
|
||||||
let range = ctx.sources.range(span);
|
let range = world.source(span.source()).range(span);
|
||||||
for error in errors.iter_mut() {
|
for error in errors.iter_mut() {
|
||||||
// Skip traces that surround the error.
|
// Skip traces that surround the error.
|
||||||
let error_range = ctx.sources.range(error.span);
|
let error_range = world.source(error.span.source()).range(error.span);
|
||||||
if range.start <= error_range.start && range.end >= error_range.end {
|
if range.start <= error_range.start && range.end >= error_range.end {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use std::fmt::{self, Debug, Formatter, Write};
|
|||||||
use std::ops::{Add, AddAssign};
|
use std::ops::{Add, AddAssign};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::{ops, Args, Func, Machine, Value};
|
use super::{ops, Args, Func, Value, Vm};
|
||||||
use crate::diag::{At, StrResult, TypResult};
|
use crate::diag::{At, StrResult, TypResult};
|
||||||
use crate::syntax::Spanned;
|
use crate::syntax::Spanned;
|
||||||
use crate::util::ArcExt;
|
use crate::util::ArcExt;
|
||||||
@ -124,7 +124,7 @@ impl Array {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return the first matching element.
|
/// Return the first matching element.
|
||||||
pub fn find(&self, vm: &mut Machine, f: Spanned<Func>) -> TypResult<Option<Value>> {
|
pub fn find(&self, vm: &mut Vm, f: Spanned<Func>) -> TypResult<Option<Value>> {
|
||||||
for item in self.iter() {
|
for item in self.iter() {
|
||||||
let args = Args::new(f.span, [item.clone()]);
|
let args = Args::new(f.span, [item.clone()]);
|
||||||
if f.v.call(vm, args)?.cast::<bool>().at(f.span)? {
|
if f.v.call(vm, args)?.cast::<bool>().at(f.span)? {
|
||||||
@ -136,7 +136,7 @@ impl Array {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return the index of the first matching element.
|
/// Return the index of the first matching element.
|
||||||
pub fn position(&self, vm: &mut Machine, f: Spanned<Func>) -> TypResult<Option<i64>> {
|
pub fn position(&self, vm: &mut Vm, f: Spanned<Func>) -> TypResult<Option<i64>> {
|
||||||
for (i, item) in self.iter().enumerate() {
|
for (i, item) in self.iter().enumerate() {
|
||||||
let args = Args::new(f.span, [item.clone()]);
|
let args = Args::new(f.span, [item.clone()]);
|
||||||
if f.v.call(vm, args)?.cast::<bool>().at(f.span)? {
|
if f.v.call(vm, args)?.cast::<bool>().at(f.span)? {
|
||||||
@ -149,7 +149,7 @@ impl Array {
|
|||||||
|
|
||||||
/// Return a new array with only those elements for which the function
|
/// Return a new array with only those elements for which the function
|
||||||
/// returns true.
|
/// returns true.
|
||||||
pub fn filter(&self, vm: &mut Machine, f: Spanned<Func>) -> TypResult<Self> {
|
pub fn filter(&self, vm: &mut Vm, f: Spanned<Func>) -> TypResult<Self> {
|
||||||
let mut kept = vec![];
|
let mut kept = vec![];
|
||||||
for item in self.iter() {
|
for item in self.iter() {
|
||||||
let args = Args::new(f.span, [item.clone()]);
|
let args = Args::new(f.span, [item.clone()]);
|
||||||
@ -161,7 +161,7 @@ impl Array {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Transform each item in the array with a function.
|
/// Transform each item in the array with a function.
|
||||||
pub fn map(&self, vm: &mut Machine, f: Spanned<Func>) -> TypResult<Self> {
|
pub fn map(&self, vm: &mut Vm, f: Spanned<Func>) -> TypResult<Self> {
|
||||||
let enumerate = f.v.argc() == Some(2);
|
let enumerate = f.v.argc() == Some(2);
|
||||||
Ok(self
|
Ok(self
|
||||||
.iter()
|
.iter()
|
||||||
@ -178,7 +178,7 @@ impl Array {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Whether any element matches.
|
/// Whether any element matches.
|
||||||
pub fn any(&self, vm: &mut Machine, f: Spanned<Func>) -> TypResult<bool> {
|
pub fn any(&self, vm: &mut Vm, f: Spanned<Func>) -> TypResult<bool> {
|
||||||
for item in self.iter() {
|
for item in self.iter() {
|
||||||
let args = Args::new(f.span, [item.clone()]);
|
let args = Args::new(f.span, [item.clone()]);
|
||||||
if f.v.call(vm, args)?.cast::<bool>().at(f.span)? {
|
if f.v.call(vm, args)?.cast::<bool>().at(f.span)? {
|
||||||
@ -190,7 +190,7 @@ impl Array {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Whether all elements match.
|
/// Whether all elements match.
|
||||||
pub fn all(&self, vm: &mut Machine, f: Spanned<Func>) -> TypResult<bool> {
|
pub fn all(&self, vm: &mut Vm, f: Spanned<Func>) -> TypResult<bool> {
|
||||||
for item in self.iter() {
|
for item in self.iter() {
|
||||||
let args = Args::new(f.span, [item.clone()]);
|
let args = Args::new(f.span, [item.clone()]);
|
||||||
if !f.v.call(vm, args)?.cast::<bool>().at(f.span)? {
|
if !f.v.call(vm, args)?.cast::<bool>().at(f.span)? {
|
||||||
|
@ -136,14 +136,14 @@ mod tests {
|
|||||||
use crate::parse::parse;
|
use crate::parse::parse;
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn test(src: &str, result: &[&str]) {
|
fn test(text: &str, result: &[&str]) {
|
||||||
let mut scopes = Scopes::new(None);
|
let mut scopes = Scopes::new(None);
|
||||||
scopes.top.define("x", 0);
|
scopes.top.define("x", 0);
|
||||||
scopes.top.define("y", 0);
|
scopes.top.define("y", 0);
|
||||||
scopes.top.define("z", 0);
|
scopes.top.define("z", 0);
|
||||||
|
|
||||||
let mut visitor = CapturesVisitor::new(&scopes);
|
let mut visitor = CapturesVisitor::new(&scopes);
|
||||||
let root = parse(src);
|
let root = parse(text);
|
||||||
visitor.visit(&root);
|
visitor.visit(&root);
|
||||||
|
|
||||||
let captures = visitor.finish();
|
let captures = visitor.finish();
|
||||||
|
@ -3,7 +3,7 @@ use std::fmt::{self, Debug, Formatter, Write};
|
|||||||
use std::ops::{Add, AddAssign};
|
use std::ops::{Add, AddAssign};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::{Args, Array, Func, Machine, Str, Value};
|
use super::{Args, Array, Func, Str, Value, Vm};
|
||||||
use crate::diag::{StrResult, TypResult};
|
use crate::diag::{StrResult, TypResult};
|
||||||
use crate::parse::is_ident;
|
use crate::parse::is_ident;
|
||||||
use crate::syntax::Spanned;
|
use crate::syntax::Spanned;
|
||||||
@ -101,7 +101,7 @@ impl Dict {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Transform each pair in the array with a function.
|
/// Transform each pair in the array with a function.
|
||||||
pub fn map(&self, vm: &mut Machine, f: Spanned<Func>) -> TypResult<Array> {
|
pub fn map(&self, vm: &mut Vm, f: Spanned<Func>) -> TypResult<Array> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(key, value)| {
|
.map(|(key, value)| {
|
||||||
|
@ -2,13 +2,13 @@ use std::fmt::{self, Debug, Formatter};
|
|||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::{Args, Eval, Flow, Machine, Scope, Scopes, Value};
|
use super::{Args, Eval, Flow, Scope, Scopes, Value, Vm};
|
||||||
use crate::diag::{StrResult, TypResult};
|
use crate::diag::{StrResult, TypResult};
|
||||||
use crate::model::{Content, NodeId, StyleMap};
|
use crate::model::{Content, NodeId, StyleMap};
|
||||||
use crate::source::SourceId;
|
use crate::source::SourceId;
|
||||||
use crate::syntax::ast::Expr;
|
use crate::syntax::ast::Expr;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
use crate::Context;
|
use crate::World;
|
||||||
|
|
||||||
/// An evaluatable function.
|
/// An evaluatable function.
|
||||||
#[derive(Clone, Hash)]
|
#[derive(Clone, Hash)]
|
||||||
@ -29,7 +29,7 @@ impl Func {
|
|||||||
/// Create a new function from a native rust function.
|
/// Create a new function from a native rust function.
|
||||||
pub fn from_fn(
|
pub fn from_fn(
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
func: fn(&mut Machine, &mut Args) -> TypResult<Value>,
|
func: fn(&mut Vm, &mut Args) -> TypResult<Value>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self(Arc::new(Repr::Native(Native {
|
Self(Arc::new(Repr::Native(Native {
|
||||||
name,
|
name,
|
||||||
@ -86,7 +86,7 @@ impl Func {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Call the function with the given arguments.
|
/// Call the function with the given arguments.
|
||||||
pub fn call(&self, vm: &mut Machine, mut args: Args) -> TypResult<Value> {
|
pub fn call(&self, vm: &mut Vm, mut args: Args) -> TypResult<Value> {
|
||||||
let value = match self.0.as_ref() {
|
let value = match self.0.as_ref() {
|
||||||
Repr::Native(native) => (native.func)(vm, &mut args)?,
|
Repr::Native(native) => (native.func)(vm, &mut args)?,
|
||||||
Repr::Closure(closure) => closure.call(vm, &mut args)?,
|
Repr::Closure(closure) => closure.call(vm, &mut args)?,
|
||||||
@ -100,8 +100,8 @@ impl Func {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Call the function without an existing virtual machine.
|
/// Call the function without an existing virtual machine.
|
||||||
pub fn call_detached(&self, ctx: &mut Context, args: Args) -> TypResult<Value> {
|
pub fn call_detached(&self, world: &dyn World, args: Args) -> TypResult<Value> {
|
||||||
let mut vm = Machine::new(ctx, vec![], Scopes::new(None));
|
let mut vm = Vm::new(world, vec![], Scopes::new(None));
|
||||||
self.call(&mut vm, args)
|
self.call(&mut vm, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,7 +144,7 @@ struct Native {
|
|||||||
/// The name of the function.
|
/// The name of the function.
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
/// The function pointer.
|
/// The function pointer.
|
||||||
pub func: fn(&mut Machine, &mut Args) -> TypResult<Value>,
|
pub func: fn(&mut Vm, &mut Args) -> TypResult<Value>,
|
||||||
/// The set rule.
|
/// The set rule.
|
||||||
pub set: Option<fn(&mut Args) -> TypResult<StyleMap>>,
|
pub set: Option<fn(&mut Args) -> TypResult<StyleMap>>,
|
||||||
/// The id of the node to customize with this function's show rule.
|
/// The id of the node to customize with this function's show rule.
|
||||||
@ -169,7 +169,7 @@ pub trait Node: 'static {
|
|||||||
///
|
///
|
||||||
/// This is passed only the arguments that remain after execution of the
|
/// This is passed only the arguments that remain after execution of the
|
||||||
/// node's set rule.
|
/// node's set rule.
|
||||||
fn construct(vm: &mut Machine, args: &mut Args) -> TypResult<Content>;
|
fn construct(vm: &mut Vm, args: &mut Args) -> TypResult<Content>;
|
||||||
|
|
||||||
/// Parse relevant arguments into style properties for this node.
|
/// Parse relevant arguments into style properties for this node.
|
||||||
///
|
///
|
||||||
@ -198,7 +198,7 @@ pub struct Closure {
|
|||||||
|
|
||||||
impl Closure {
|
impl Closure {
|
||||||
/// Call the function in the context with the arguments.
|
/// Call the function in the context with the arguments.
|
||||||
pub fn call(&self, vm: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn call(&self, vm: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
// Don't leak the scopes from the call site. Instead, we use the scope
|
// Don't leak the scopes from the call site. Instead, we use the scope
|
||||||
// of captured variables we collected earlier.
|
// of captured variables we collected earlier.
|
||||||
let mut scopes = Scopes::new(None);
|
let mut scopes = Scopes::new(None);
|
||||||
@ -228,9 +228,8 @@ impl Closure {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Evaluate the body.
|
// Evaluate the body.
|
||||||
let mut sub = Machine::new(vm.ctx, route, scopes);
|
let mut sub = Vm::new(vm.world, route, scopes);
|
||||||
let result = self.body.eval(&mut sub);
|
let result = self.body.eval(&mut sub);
|
||||||
vm.deps.extend(sub.deps);
|
|
||||||
|
|
||||||
// Handle control flow.
|
// Handle control flow.
|
||||||
match sub.flow {
|
match sub.flow {
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
//! Methods on values.
|
//! Methods on values.
|
||||||
|
|
||||||
use super::{Args, Machine, Value};
|
use super::{Args, Value, Vm};
|
||||||
use crate::diag::{At, TypResult};
|
use crate::diag::{At, TypResult};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
/// Call a method on a value.
|
/// Call a method on a value.
|
||||||
pub fn call(
|
pub fn call(
|
||||||
vm: &mut Machine,
|
vm: &mut Vm,
|
||||||
value: Value,
|
value: Value,
|
||||||
method: &str,
|
method: &str,
|
||||||
mut args: Args,
|
mut args: Args,
|
||||||
|
153
src/eval/mod.rs
153
src/eval/mod.rs
@ -13,11 +13,11 @@ mod value;
|
|||||||
mod args;
|
mod args;
|
||||||
mod capture;
|
mod capture;
|
||||||
mod func;
|
mod func;
|
||||||
mod machine;
|
|
||||||
pub mod methods;
|
pub mod methods;
|
||||||
pub mod ops;
|
pub mod ops;
|
||||||
mod raw;
|
mod raw;
|
||||||
mod scope;
|
mod scope;
|
||||||
|
mod vm;
|
||||||
|
|
||||||
pub use self::str::*;
|
pub use self::str::*;
|
||||||
pub use args::*;
|
pub use args::*;
|
||||||
@ -26,25 +26,25 @@ pub use capture::*;
|
|||||||
pub use cast::*;
|
pub use cast::*;
|
||||||
pub use dict::*;
|
pub use dict::*;
|
||||||
pub use func::*;
|
pub use func::*;
|
||||||
pub use machine::*;
|
|
||||||
pub use raw::*;
|
pub use raw::*;
|
||||||
pub use scope::*;
|
pub use scope::*;
|
||||||
pub use typst_macros::node;
|
pub use typst_macros::node;
|
||||||
pub use value::*;
|
pub use value::*;
|
||||||
|
pub use vm::*;
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use crate::diag::{At, StrResult, Trace, Tracepoint, TypResult};
|
use crate::diag::{failed_to_load, At, StrResult, Trace, Tracepoint, TypResult};
|
||||||
use crate::geom::{Angle, Em, Fraction, Length, Ratio};
|
use crate::geom::{Angle, Em, Fraction, Length, Ratio};
|
||||||
use crate::library;
|
use crate::library;
|
||||||
use crate::model::{Content, Pattern, Recipe, StyleEntry, StyleMap};
|
use crate::model::{Content, Pattern, Recipe, StyleEntry, StyleMap};
|
||||||
use crate::source::{SourceId, SourceStore};
|
use crate::source::SourceId;
|
||||||
use crate::syntax::ast::*;
|
use crate::syntax::ast::*;
|
||||||
use crate::syntax::{Span, Spanned};
|
use crate::syntax::{Span, Spanned};
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
use crate::Context;
|
use crate::World;
|
||||||
|
|
||||||
/// Evaluate a source file and return the resulting module.
|
/// Evaluate a source file and return the resulting module.
|
||||||
///
|
///
|
||||||
@ -52,29 +52,24 @@ use crate::Context;
|
|||||||
/// layoutable contents or diagnostics in the form of a vector of error
|
/// layoutable contents or diagnostics in the form of a vector of error
|
||||||
/// messages with file and span information.
|
/// messages with file and span information.
|
||||||
pub fn evaluate(
|
pub fn evaluate(
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
id: SourceId,
|
id: SourceId,
|
||||||
mut route: Vec<SourceId>,
|
mut route: Vec<SourceId>,
|
||||||
) -> TypResult<Module> {
|
) -> TypResult<Module> {
|
||||||
// Prevent cyclic evaluation.
|
// Prevent cyclic evaluation.
|
||||||
if route.contains(&id) {
|
if route.contains(&id) {
|
||||||
let path = ctx.sources.get(id).path().display();
|
let path = world.source(id).path().display();
|
||||||
panic!("Tried to cyclicly evaluate {}", path);
|
panic!("Tried to cyclicly evaluate {}", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
route.push(id);
|
route.push(id);
|
||||||
|
|
||||||
// Parse the file.
|
|
||||||
let source = ctx.sources.get(id);
|
|
||||||
let ast = source.ast()?;
|
|
||||||
let rev = source.rev();
|
|
||||||
|
|
||||||
// Evaluate the module.
|
// Evaluate the module.
|
||||||
let std = ctx.config.std.clone();
|
let ast = world.source(id).ast()?;
|
||||||
let scopes = Scopes::new(Some(&std));
|
let std = &world.config().std;
|
||||||
let mut vm = Machine::new(ctx, route, scopes);
|
let scopes = Scopes::new(Some(std));
|
||||||
|
let mut vm = Vm::new(world, route, scopes);
|
||||||
let result = ast.eval(&mut vm);
|
let result = ast.eval(&mut vm);
|
||||||
vm.deps.push((id, rev));
|
|
||||||
|
|
||||||
// Handle control flow.
|
// Handle control flow.
|
||||||
if let Some(flow) = vm.flow {
|
if let Some(flow) = vm.flow {
|
||||||
@ -82,11 +77,7 @@ pub fn evaluate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Assemble the module.
|
// Assemble the module.
|
||||||
Ok(Module {
|
Ok(Module { scope: vm.scopes.top, content: result? })
|
||||||
scope: vm.scopes.top,
|
|
||||||
content: result?,
|
|
||||||
deps: vm.deps,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An evaluated module, ready for importing or layouting.
|
/// An evaluated module, ready for importing or layouting.
|
||||||
@ -96,15 +87,6 @@ pub struct Module {
|
|||||||
pub scope: Scope,
|
pub scope: Scope,
|
||||||
/// The module's layoutable contents.
|
/// The module's layoutable contents.
|
||||||
pub content: Content,
|
pub content: Content,
|
||||||
/// The source file revisions this module depends on.
|
|
||||||
pub deps: Vec<(SourceId, usize)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Module {
|
|
||||||
/// Whether the module is still valid for the given sources.
|
|
||||||
pub fn valid(&self, sources: &SourceStore) -> bool {
|
|
||||||
self.deps.iter().all(|&(id, rev)| rev == sources.get(id).rev())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate an expression.
|
/// Evaluate an expression.
|
||||||
@ -113,20 +95,20 @@ pub trait Eval {
|
|||||||
type Output;
|
type Output;
|
||||||
|
|
||||||
/// Evaluate the expression to the output value.
|
/// Evaluate the expression to the output value.
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output>;
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eval for Markup {
|
impl Eval for Markup {
|
||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
eval_markup(vm, &mut self.nodes())
|
eval_markup(vm, &mut self.nodes())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a stream of markup nodes.
|
/// Evaluate a stream of markup nodes.
|
||||||
fn eval_markup(
|
fn eval_markup(
|
||||||
vm: &mut Machine,
|
vm: &mut Vm,
|
||||||
nodes: &mut impl Iterator<Item = MarkupNode>,
|
nodes: &mut impl Iterator<Item = MarkupNode>,
|
||||||
) -> TypResult<Content> {
|
) -> TypResult<Content> {
|
||||||
let flow = vm.flow.take();
|
let flow = vm.flow.take();
|
||||||
@ -175,7 +157,7 @@ fn eval_markup(
|
|||||||
impl Eval for MarkupNode {
|
impl Eval for MarkupNode {
|
||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
Self::Space => Content::Space,
|
Self::Space => Content::Space,
|
||||||
Self::Parbreak => Content::Parbreak,
|
Self::Parbreak => Content::Parbreak,
|
||||||
@ -199,7 +181,7 @@ impl Eval for MarkupNode {
|
|||||||
impl Eval for StrongNode {
|
impl Eval for StrongNode {
|
||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
Ok(Content::show(library::text::StrongNode(
|
Ok(Content::show(library::text::StrongNode(
|
||||||
self.body().eval(vm)?,
|
self.body().eval(vm)?,
|
||||||
)))
|
)))
|
||||||
@ -209,7 +191,7 @@ impl Eval for StrongNode {
|
|||||||
impl Eval for EmphNode {
|
impl Eval for EmphNode {
|
||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
Ok(Content::show(library::text::EmphNode(
|
Ok(Content::show(library::text::EmphNode(
|
||||||
self.body().eval(vm)?,
|
self.body().eval(vm)?,
|
||||||
)))
|
)))
|
||||||
@ -219,7 +201,7 @@ impl Eval for EmphNode {
|
|||||||
impl Eval for RawNode {
|
impl Eval for RawNode {
|
||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, _: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, _: &mut Vm) -> TypResult<Self::Output> {
|
||||||
let content = Content::show(library::text::RawNode {
|
let content = Content::show(library::text::RawNode {
|
||||||
text: self.text.clone(),
|
text: self.text.clone(),
|
||||||
block: self.block,
|
block: self.block,
|
||||||
@ -234,7 +216,7 @@ impl Eval for RawNode {
|
|||||||
impl Eval for Spanned<MathNode> {
|
impl Eval for Spanned<MathNode> {
|
||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, _: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, _: &mut Vm) -> TypResult<Self::Output> {
|
||||||
Ok(Content::show(library::math::MathNode {
|
Ok(Content::show(library::math::MathNode {
|
||||||
formula: self.clone().map(|math| math.formula),
|
formula: self.clone().map(|math| math.formula),
|
||||||
display: self.v.display,
|
display: self.v.display,
|
||||||
@ -245,7 +227,7 @@ impl Eval for Spanned<MathNode> {
|
|||||||
impl Eval for HeadingNode {
|
impl Eval for HeadingNode {
|
||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
Ok(Content::show(library::structure::HeadingNode {
|
Ok(Content::show(library::structure::HeadingNode {
|
||||||
body: self.body().eval(vm)?,
|
body: self.body().eval(vm)?,
|
||||||
level: self.level(),
|
level: self.level(),
|
||||||
@ -256,7 +238,7 @@ impl Eval for HeadingNode {
|
|||||||
impl Eval for ListNode {
|
impl Eval for ListNode {
|
||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
Ok(Content::Item(library::structure::ListItem {
|
Ok(Content::Item(library::structure::ListItem {
|
||||||
kind: library::structure::UNORDERED,
|
kind: library::structure::UNORDERED,
|
||||||
number: None,
|
number: None,
|
||||||
@ -268,7 +250,7 @@ impl Eval for ListNode {
|
|||||||
impl Eval for EnumNode {
|
impl Eval for EnumNode {
|
||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
Ok(Content::Item(library::structure::ListItem {
|
Ok(Content::Item(library::structure::ListItem {
|
||||||
kind: library::structure::ORDERED,
|
kind: library::structure::ORDERED,
|
||||||
number: self.number(),
|
number: self.number(),
|
||||||
@ -280,7 +262,7 @@ impl Eval for EnumNode {
|
|||||||
impl Eval for Expr {
|
impl Eval for Expr {
|
||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
let forbidden = |name| {
|
let forbidden = |name| {
|
||||||
error!(
|
error!(
|
||||||
self.span(),
|
self.span(),
|
||||||
@ -321,7 +303,7 @@ impl Eval for Expr {
|
|||||||
impl Eval for Lit {
|
impl Eval for Lit {
|
||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, _: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, _: &mut Vm) -> TypResult<Self::Output> {
|
||||||
Ok(match self.kind() {
|
Ok(match self.kind() {
|
||||||
LitKind::None => Value::None,
|
LitKind::None => Value::None,
|
||||||
LitKind::Auto => Value::Auto,
|
LitKind::Auto => Value::Auto,
|
||||||
@ -343,7 +325,7 @@ impl Eval for Lit {
|
|||||||
impl Eval for Ident {
|
impl Eval for Ident {
|
||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
vm.scopes.get(self).cloned().at(self.span())
|
vm.scopes.get(self).cloned().at(self.span())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -351,7 +333,7 @@ impl Eval for Ident {
|
|||||||
impl Eval for CodeBlock {
|
impl Eval for CodeBlock {
|
||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
vm.scopes.enter();
|
vm.scopes.enter();
|
||||||
let output = eval_code(vm, &mut self.exprs())?;
|
let output = eval_code(vm, &mut self.exprs())?;
|
||||||
vm.scopes.exit();
|
vm.scopes.exit();
|
||||||
@ -360,10 +342,7 @@ impl Eval for CodeBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a stream of expressions.
|
/// Evaluate a stream of expressions.
|
||||||
fn eval_code(
|
fn eval_code(vm: &mut Vm, exprs: &mut impl Iterator<Item = Expr>) -> TypResult<Value> {
|
||||||
vm: &mut Machine,
|
|
||||||
exprs: &mut impl Iterator<Item = Expr>,
|
|
||||||
) -> TypResult<Value> {
|
|
||||||
let flow = vm.flow.take();
|
let flow = vm.flow.take();
|
||||||
let mut output = Value::None;
|
let mut output = Value::None;
|
||||||
|
|
||||||
@ -415,7 +394,7 @@ fn eval_code(
|
|||||||
impl Eval for ContentBlock {
|
impl Eval for ContentBlock {
|
||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
vm.scopes.enter();
|
vm.scopes.enter();
|
||||||
let content = self.body().eval(vm)?;
|
let content = self.body().eval(vm)?;
|
||||||
vm.scopes.exit();
|
vm.scopes.exit();
|
||||||
@ -426,7 +405,7 @@ impl Eval for ContentBlock {
|
|||||||
impl Eval for GroupExpr {
|
impl Eval for GroupExpr {
|
||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
self.expr().eval(vm)
|
self.expr().eval(vm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -434,7 +413,7 @@ impl Eval for GroupExpr {
|
|||||||
impl Eval for ArrayExpr {
|
impl Eval for ArrayExpr {
|
||||||
type Output = Array;
|
type Output = Array;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
let items = self.items();
|
let items = self.items();
|
||||||
|
|
||||||
let mut vec = Vec::with_capacity(items.size_hint().0);
|
let mut vec = Vec::with_capacity(items.size_hint().0);
|
||||||
@ -456,7 +435,7 @@ impl Eval for ArrayExpr {
|
|||||||
impl Eval for DictExpr {
|
impl Eval for DictExpr {
|
||||||
type Output = Dict;
|
type Output = Dict;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
let mut map = BTreeMap::new();
|
let mut map = BTreeMap::new();
|
||||||
|
|
||||||
for item in self.items() {
|
for item in self.items() {
|
||||||
@ -486,7 +465,7 @@ impl Eval for DictExpr {
|
|||||||
impl Eval for UnaryExpr {
|
impl Eval for UnaryExpr {
|
||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
let value = self.expr().eval(vm)?;
|
let value = self.expr().eval(vm)?;
|
||||||
let result = match self.op() {
|
let result = match self.op() {
|
||||||
UnOp::Pos => ops::pos(value),
|
UnOp::Pos => ops::pos(value),
|
||||||
@ -500,7 +479,7 @@ impl Eval for UnaryExpr {
|
|||||||
impl Eval for BinaryExpr {
|
impl Eval for BinaryExpr {
|
||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
match self.op() {
|
match self.op() {
|
||||||
BinOp::Add => self.apply(vm, ops::add),
|
BinOp::Add => self.apply(vm, ops::add),
|
||||||
BinOp::Sub => self.apply(vm, ops::sub),
|
BinOp::Sub => self.apply(vm, ops::sub),
|
||||||
@ -529,7 +508,7 @@ impl BinaryExpr {
|
|||||||
/// Apply a basic binary operation.
|
/// Apply a basic binary operation.
|
||||||
fn apply(
|
fn apply(
|
||||||
&self,
|
&self,
|
||||||
vm: &mut Machine,
|
vm: &mut Vm,
|
||||||
op: fn(Value, Value) -> StrResult<Value>,
|
op: fn(Value, Value) -> StrResult<Value>,
|
||||||
) -> TypResult<Value> {
|
) -> TypResult<Value> {
|
||||||
let lhs = self.lhs().eval(vm)?;
|
let lhs = self.lhs().eval(vm)?;
|
||||||
@ -548,7 +527,7 @@ impl BinaryExpr {
|
|||||||
/// Apply an assignment operation.
|
/// Apply an assignment operation.
|
||||||
fn assign(
|
fn assign(
|
||||||
&self,
|
&self,
|
||||||
vm: &mut Machine,
|
vm: &mut Vm,
|
||||||
op: fn(Value, Value) -> StrResult<Value>,
|
op: fn(Value, Value) -> StrResult<Value>,
|
||||||
) -> TypResult<Value> {
|
) -> TypResult<Value> {
|
||||||
let rhs = self.rhs().eval(vm)?;
|
let rhs = self.rhs().eval(vm)?;
|
||||||
@ -562,7 +541,7 @@ impl BinaryExpr {
|
|||||||
impl Eval for FieldAccess {
|
impl Eval for FieldAccess {
|
||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
let object = self.object().eval(vm)?;
|
let object = self.object().eval(vm)?;
|
||||||
let span = self.field().span();
|
let span = self.field().span();
|
||||||
let field = self.field().take();
|
let field = self.field().take();
|
||||||
@ -588,7 +567,7 @@ impl Eval for FieldAccess {
|
|||||||
impl Eval for FuncCall {
|
impl Eval for FuncCall {
|
||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
let callee = self.callee().eval(vm)?;
|
let callee = self.callee().eval(vm)?;
|
||||||
let args = self.args().eval(vm)?;
|
let args = self.args().eval(vm)?;
|
||||||
|
|
||||||
@ -597,7 +576,7 @@ impl Eval for FuncCall {
|
|||||||
Value::Dict(dict) => dict.get(&args.into_key()?).at(self.span())?.clone(),
|
Value::Dict(dict) => dict.get(&args.into_key()?).at(self.span())?.clone(),
|
||||||
Value::Func(func) => {
|
Value::Func(func) => {
|
||||||
let point = || Tracepoint::Call(func.name().map(ToString::to_string));
|
let point = || Tracepoint::Call(func.name().map(ToString::to_string));
|
||||||
func.call(vm, args).trace(vm.ctx, point, self.span())?
|
func.call(vm, args).trace(vm.world, point, self.span())?
|
||||||
}
|
}
|
||||||
|
|
||||||
v => bail!(
|
v => bail!(
|
||||||
@ -612,7 +591,7 @@ impl Eval for FuncCall {
|
|||||||
impl Eval for MethodCall {
|
impl Eval for MethodCall {
|
||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
let span = self.span();
|
let span = self.span();
|
||||||
let method = self.method();
|
let method = self.method();
|
||||||
let point = || Tracepoint::Call(Some(method.to_string()));
|
let point = || Tracepoint::Call(Some(method.to_string()));
|
||||||
@ -621,12 +600,12 @@ impl Eval for MethodCall {
|
|||||||
let args = self.args().eval(vm)?;
|
let args = self.args().eval(vm)?;
|
||||||
let mut value = self.receiver().access(vm)?;
|
let mut value = self.receiver().access(vm)?;
|
||||||
methods::call_mut(&mut value, &method, args, span)
|
methods::call_mut(&mut value, &method, args, span)
|
||||||
.trace(vm.ctx, point, span)?;
|
.trace(vm.world, point, span)?;
|
||||||
Value::None
|
Value::None
|
||||||
} else {
|
} else {
|
||||||
let value = self.receiver().eval(vm)?;
|
let value = self.receiver().eval(vm)?;
|
||||||
let args = self.args().eval(vm)?;
|
let args = self.args().eval(vm)?;
|
||||||
methods::call(vm, value, &method, args, span).trace(vm.ctx, point, span)?
|
methods::call(vm, value, &method, args, span).trace(vm.world, point, span)?
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -634,7 +613,7 @@ impl Eval for MethodCall {
|
|||||||
impl Eval for CallArgs {
|
impl Eval for CallArgs {
|
||||||
type Output = Args;
|
type Output = Args;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
let mut items = Vec::new();
|
let mut items = Vec::new();
|
||||||
|
|
||||||
for arg in self.items() {
|
for arg in self.items() {
|
||||||
@ -683,7 +662,7 @@ impl Eval for CallArgs {
|
|||||||
impl Eval for ClosureExpr {
|
impl Eval for ClosureExpr {
|
||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
// The closure's name is defined by its let binding if there's one.
|
// The closure's name is defined by its let binding if there's one.
|
||||||
let name = self.name().map(Ident::take);
|
let name = self.name().map(Ident::take);
|
||||||
|
|
||||||
@ -730,7 +709,7 @@ impl Eval for ClosureExpr {
|
|||||||
impl Eval for LetExpr {
|
impl Eval for LetExpr {
|
||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
let value = match self.init() {
|
let value = match self.init() {
|
||||||
Some(expr) => expr.eval(vm)?,
|
Some(expr) => expr.eval(vm)?,
|
||||||
None => Value::None,
|
None => Value::None,
|
||||||
@ -743,7 +722,7 @@ impl Eval for LetExpr {
|
|||||||
impl Eval for SetExpr {
|
impl Eval for SetExpr {
|
||||||
type Output = StyleMap;
|
type Output = StyleMap;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
let target = self.target();
|
let target = self.target();
|
||||||
let target = target.eval(vm)?.cast::<Func>().at(target.span())?;
|
let target = target.eval(vm)?.cast::<Func>().at(target.span())?;
|
||||||
let args = self.args().eval(vm)?;
|
let args = self.args().eval(vm)?;
|
||||||
@ -754,7 +733,7 @@ impl Eval for SetExpr {
|
|||||||
impl Eval for ShowExpr {
|
impl Eval for ShowExpr {
|
||||||
type Output = Recipe;
|
type Output = Recipe;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
// Evaluate the target function.
|
// Evaluate the target function.
|
||||||
let pattern = self.pattern();
|
let pattern = self.pattern();
|
||||||
let pattern = pattern.eval(vm)?.cast::<Pattern>().at(pattern.span())?;
|
let pattern = pattern.eval(vm)?.cast::<Pattern>().at(pattern.span())?;
|
||||||
@ -791,7 +770,7 @@ impl Eval for ShowExpr {
|
|||||||
impl Eval for IfExpr {
|
impl Eval for IfExpr {
|
||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
let condition = self.condition();
|
let condition = self.condition();
|
||||||
if condition.eval(vm)?.cast::<bool>().at(condition.span())? {
|
if condition.eval(vm)?.cast::<bool>().at(condition.span())? {
|
||||||
self.if_body().eval(vm)
|
self.if_body().eval(vm)
|
||||||
@ -806,7 +785,7 @@ impl Eval for IfExpr {
|
|||||||
impl Eval for WhileExpr {
|
impl Eval for WhileExpr {
|
||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
let flow = vm.flow.take();
|
let flow = vm.flow.take();
|
||||||
let mut output = Value::None;
|
let mut output = Value::None;
|
||||||
|
|
||||||
@ -838,7 +817,7 @@ impl Eval for WhileExpr {
|
|||||||
impl Eval for ForExpr {
|
impl Eval for ForExpr {
|
||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
let flow = vm.flow.take();
|
let flow = vm.flow.take();
|
||||||
let mut output = Value::None;
|
let mut output = Value::None;
|
||||||
vm.scopes.enter();
|
vm.scopes.enter();
|
||||||
@ -917,7 +896,7 @@ impl Eval for ForExpr {
|
|||||||
impl Eval for ImportExpr {
|
impl Eval for ImportExpr {
|
||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
let span = self.path().span();
|
let span = self.path().span();
|
||||||
let path = self.path().eval(vm)?.cast::<EcoString>().at(span)?;
|
let path = self.path().eval(vm)?.cast::<EcoString>().at(span)?;
|
||||||
let module = import(vm, &path, span)?;
|
let module = import(vm, &path, span)?;
|
||||||
@ -946,7 +925,7 @@ impl Eval for ImportExpr {
|
|||||||
impl Eval for IncludeExpr {
|
impl Eval for IncludeExpr {
|
||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
let span = self.path().span();
|
let span = self.path().span();
|
||||||
let path = self.path().eval(vm)?.cast::<EcoString>().at(span)?;
|
let path = self.path().eval(vm)?.cast::<EcoString>().at(span)?;
|
||||||
let module = import(vm, &path, span)?;
|
let module = import(vm, &path, span)?;
|
||||||
@ -955,10 +934,14 @@ impl Eval for IncludeExpr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Process an import of a module relative to the current location.
|
/// Process an import of a module relative to the current location.
|
||||||
fn import(vm: &mut Machine, path: &str, span: Span) -> TypResult<Module> {
|
fn import(vm: &mut Vm, path: &str, span: Span) -> TypResult<Module> {
|
||||||
// Load the source file.
|
// Load the source file.
|
||||||
let full = vm.locate(&path).at(span)?;
|
let full = vm.locate(&path).at(span)?;
|
||||||
let id = vm.ctx.sources.load(&full).at(span)?;
|
let id = vm
|
||||||
|
.world
|
||||||
|
.resolve(&full)
|
||||||
|
.map_err(|err| failed_to_load("source file", &full, err))
|
||||||
|
.at(span)?;
|
||||||
|
|
||||||
// Prevent cyclic importing.
|
// Prevent cyclic importing.
|
||||||
if vm.route.contains(&id) {
|
if vm.route.contains(&id) {
|
||||||
@ -968,9 +951,7 @@ fn import(vm: &mut Machine, path: &str, span: Span) -> TypResult<Module> {
|
|||||||
// Evaluate the file.
|
// Evaluate the file.
|
||||||
let route = vm.route.clone();
|
let route = vm.route.clone();
|
||||||
let module =
|
let module =
|
||||||
evaluate(vm.ctx, id, route).trace(vm.ctx, || Tracepoint::Import, span)?;
|
evaluate(vm.world, id, route).trace(vm.world, || Tracepoint::Import, span)?;
|
||||||
|
|
||||||
vm.deps.extend(module.deps.iter().cloned());
|
|
||||||
|
|
||||||
Ok(module)
|
Ok(module)
|
||||||
}
|
}
|
||||||
@ -978,7 +959,7 @@ fn import(vm: &mut Machine, path: &str, span: Span) -> TypResult<Module> {
|
|||||||
impl Eval for BreakExpr {
|
impl Eval for BreakExpr {
|
||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
if vm.flow.is_none() {
|
if vm.flow.is_none() {
|
||||||
vm.flow = Some(Flow::Break(self.span()));
|
vm.flow = Some(Flow::Break(self.span()));
|
||||||
}
|
}
|
||||||
@ -989,7 +970,7 @@ impl Eval for BreakExpr {
|
|||||||
impl Eval for ContinueExpr {
|
impl Eval for ContinueExpr {
|
||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
if vm.flow.is_none() {
|
if vm.flow.is_none() {
|
||||||
vm.flow = Some(Flow::Continue(self.span()));
|
vm.flow = Some(Flow::Continue(self.span()));
|
||||||
}
|
}
|
||||||
@ -1000,7 +981,7 @@ impl Eval for ContinueExpr {
|
|||||||
impl Eval for ReturnExpr {
|
impl Eval for ReturnExpr {
|
||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
let value = self.body().map(|body| body.eval(vm)).transpose()?;
|
let value = self.body().map(|body| body.eval(vm)).transpose()?;
|
||||||
if vm.flow.is_none() {
|
if vm.flow.is_none() {
|
||||||
vm.flow = Some(Flow::Return(self.span(), value));
|
vm.flow = Some(Flow::Return(self.span(), value));
|
||||||
@ -1012,11 +993,11 @@ impl Eval for ReturnExpr {
|
|||||||
/// Access an expression mutably.
|
/// Access an expression mutably.
|
||||||
pub trait Access {
|
pub trait Access {
|
||||||
/// Access the value.
|
/// Access the value.
|
||||||
fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<&'a mut Value>;
|
fn access<'a>(&self, vm: &'a mut Vm) -> TypResult<&'a mut Value>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Access for Expr {
|
impl Access for Expr {
|
||||||
fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<&'a mut Value> {
|
fn access<'a>(&self, vm: &'a mut Vm) -> TypResult<&'a mut Value> {
|
||||||
match self {
|
match self {
|
||||||
Expr::Ident(v) => v.access(vm),
|
Expr::Ident(v) => v.access(vm),
|
||||||
Expr::FieldAccess(v) => v.access(vm),
|
Expr::FieldAccess(v) => v.access(vm),
|
||||||
@ -1027,13 +1008,13 @@ impl Access for Expr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Access for Ident {
|
impl Access for Ident {
|
||||||
fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<&'a mut Value> {
|
fn access<'a>(&self, vm: &'a mut Vm) -> TypResult<&'a mut Value> {
|
||||||
vm.scopes.get_mut(self).at(self.span())
|
vm.scopes.get_mut(self).at(self.span())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Access for FieldAccess {
|
impl Access for FieldAccess {
|
||||||
fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<&'a mut Value> {
|
fn access<'a>(&self, vm: &'a mut Vm) -> TypResult<&'a mut Value> {
|
||||||
Ok(match self.object().access(vm)? {
|
Ok(match self.object().access(vm)? {
|
||||||
Value::Dict(dict) => dict.get_mut(self.field().take().into()),
|
Value::Dict(dict) => dict.get_mut(self.field().take().into()),
|
||||||
v => bail!(
|
v => bail!(
|
||||||
@ -1046,7 +1027,7 @@ impl Access for FieldAccess {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Access for FuncCall {
|
impl Access for FuncCall {
|
||||||
fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<&'a mut Value> {
|
fn access<'a>(&self, vm: &'a mut Vm) -> TypResult<&'a mut Value> {
|
||||||
let args = self.args().eval(vm)?;
|
let args = self.args().eval(vm)?;
|
||||||
Ok(match self.callee().access(vm)? {
|
Ok(match self.callee().access(vm)? {
|
||||||
Value::Array(array) => array.get_mut(args.into_index()?).at(self.span())?,
|
Value::Array(array) => array.get_mut(args.into_index()?).at(self.span())?,
|
||||||
|
@ -2,7 +2,7 @@ use std::collections::BTreeMap;
|
|||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
use super::{Args, Func, Machine, Node, Value};
|
use super::{Args, Func, Node, Value, Vm};
|
||||||
use crate::diag::{StrResult, TypResult};
|
use crate::diag::{StrResult, TypResult};
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ impl Scope {
|
|||||||
pub fn def_fn(
|
pub fn def_fn(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
func: fn(&mut Machine, &mut Args) -> TypResult<Value>,
|
func: fn(&mut Vm, &mut Args) -> TypResult<Value>,
|
||||||
) {
|
) {
|
||||||
self.define(name, Func::from_fn(name, func));
|
self.define(name, Func::from_fn(name, func));
|
||||||
}
|
}
|
||||||
|
@ -5,32 +5,24 @@ use crate::diag::{StrResult, TypError};
|
|||||||
use crate::source::SourceId;
|
use crate::source::SourceId;
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
use crate::util::PathExt;
|
use crate::util::PathExt;
|
||||||
use crate::Context;
|
use crate::World;
|
||||||
|
|
||||||
/// A virtual machine.
|
/// A virtual machine.
|
||||||
pub struct Machine<'a> {
|
pub struct Vm<'w> {
|
||||||
/// The core context.
|
/// The core context.
|
||||||
pub ctx: &'a mut Context,
|
pub world: &'w dyn World,
|
||||||
/// The route of source ids the machine took to reach its current location.
|
/// The route of source ids the machine took to reach its current location.
|
||||||
pub route: Vec<SourceId>,
|
pub route: Vec<SourceId>,
|
||||||
/// The dependencies of the current evaluation process.
|
|
||||||
pub deps: Vec<(SourceId, usize)>,
|
|
||||||
/// The stack of scopes.
|
/// The stack of scopes.
|
||||||
pub scopes: Scopes<'a>,
|
pub scopes: Scopes<'w>,
|
||||||
/// A control flow event that is currently happening.
|
/// A control flow event that is currently happening.
|
||||||
pub flow: Option<Flow>,
|
pub flow: Option<Flow>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Machine<'a> {
|
impl<'w> Vm<'w> {
|
||||||
/// Create a new virtual machine.
|
/// Create a new virtual machine.
|
||||||
pub fn new(ctx: &'a mut Context, route: Vec<SourceId>, scopes: Scopes<'a>) -> Self {
|
pub fn new(ctx: &'w dyn World, route: Vec<SourceId>, scopes: Scopes<'w>) -> Self {
|
||||||
Self {
|
Self { world: ctx, route, scopes, flow: None }
|
||||||
ctx,
|
|
||||||
route,
|
|
||||||
deps: vec![],
|
|
||||||
scopes,
|
|
||||||
flow: None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve a user-entered path to be relative to the compilation
|
/// Resolve a user-entered path to be relative to the compilation
|
||||||
@ -38,10 +30,10 @@ impl<'a> Machine<'a> {
|
|||||||
pub fn locate(&self, path: &str) -> StrResult<PathBuf> {
|
pub fn locate(&self, path: &str) -> StrResult<PathBuf> {
|
||||||
if let Some(&id) = self.route.last() {
|
if let Some(&id) = self.route.last() {
|
||||||
if let Some(path) = path.strip_prefix('/') {
|
if let Some(path) = path.strip_prefix('/') {
|
||||||
return Ok(self.ctx.config.root.join(path).normalize());
|
return Ok(self.world.config().root.join(path).normalize());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(dir) = self.ctx.sources.get(id).path().parent() {
|
if let Some(dir) = self.world.source(id).path().parent() {
|
||||||
return Ok(dir.join(path).normalize());
|
return Ok(dir.join(path).normalize());
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -16,48 +16,40 @@ pub fn write_images(ctx: &mut PdfContext) {
|
|||||||
let height = image.height();
|
let height = image.height();
|
||||||
|
|
||||||
// Add the primary image.
|
// Add the primary image.
|
||||||
|
// TODO: Error if image could not be encoded.
|
||||||
match image.decode().unwrap() {
|
match image.decode().unwrap() {
|
||||||
DecodedImage::Raster(dynamic) => {
|
DecodedImage::Raster(dynamic) => {
|
||||||
if let Ok((data, filter, has_color)) =
|
// TODO: Error if image could not be encoded.
|
||||||
encode_image(image.format(), &dynamic)
|
let (data, filter, has_color) =
|
||||||
{
|
encode_image(image.format(), &dynamic).unwrap();
|
||||||
let mut image = ctx.writer.image_xobject(image_ref, &data);
|
|
||||||
image.filter(filter);
|
|
||||||
image.width(width as i32);
|
|
||||||
image.height(height as i32);
|
|
||||||
image.bits_per_component(8);
|
|
||||||
|
|
||||||
let space = image.color_space();
|
let mut image = ctx.writer.image_xobject(image_ref, &data);
|
||||||
if has_color {
|
image.filter(filter);
|
||||||
space.device_rgb();
|
image.width(width as i32);
|
||||||
} else {
|
image.height(height as i32);
|
||||||
space.device_gray();
|
image.bits_per_component(8);
|
||||||
}
|
|
||||||
|
|
||||||
// Add a second gray-scale image containing the alpha values if
|
let space = image.color_space();
|
||||||
// this image has an alpha channel.
|
if has_color {
|
||||||
if dynamic.color().has_alpha() {
|
space.device_rgb();
|
||||||
let (alpha_data, alpha_filter) = encode_alpha(&dynamic);
|
|
||||||
let mask_ref = ctx.alloc.bump();
|
|
||||||
image.s_mask(mask_ref);
|
|
||||||
image.finish();
|
|
||||||
|
|
||||||
let mut mask = ctx.writer.image_xobject(mask_ref, &alpha_data);
|
|
||||||
mask.filter(alpha_filter);
|
|
||||||
mask.width(width as i32);
|
|
||||||
mask.height(height as i32);
|
|
||||||
mask.color_space().device_gray();
|
|
||||||
mask.bits_per_component(8);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// TODO: Warn that image could not be encoded.
|
space.device_gray();
|
||||||
ctx.writer
|
}
|
||||||
.image_xobject(image_ref, &[])
|
|
||||||
.width(0)
|
// Add a second gray-scale image containing the alpha values if
|
||||||
.height(0)
|
// this image has an alpha channel.
|
||||||
.bits_per_component(1)
|
if dynamic.color().has_alpha() {
|
||||||
.color_space()
|
let (alpha_data, alpha_filter) = encode_alpha(&dynamic);
|
||||||
.device_gray();
|
let mask_ref = ctx.alloc.bump();
|
||||||
|
image.s_mask(mask_ref);
|
||||||
|
image.finish();
|
||||||
|
|
||||||
|
let mut mask = ctx.writer.image_xobject(mask_ref, &alpha_data);
|
||||||
|
mask.filter(alpha_filter);
|
||||||
|
mask.width(width as i32);
|
||||||
|
mask.height(height as i32);
|
||||||
|
mask.color_space().device_gray();
|
||||||
|
mask.bits_per_component(8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DecodedImage::Svg(svg) => {
|
DecodedImage::Svg(svg) => {
|
||||||
|
@ -147,8 +147,8 @@ fn render_svg_glyph(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse XML.
|
// Parse XML.
|
||||||
let src = std::str::from_utf8(data).ok()?;
|
let xml = std::str::from_utf8(data).ok()?;
|
||||||
let document = roxmltree::Document::parse(src).ok()?;
|
let document = roxmltree::Document::parse(xml).ok()?;
|
||||||
let root = document.root_element();
|
let root = document.root_element();
|
||||||
|
|
||||||
// Parse SVG.
|
// Parse SVG.
|
||||||
|
@ -39,8 +39,8 @@ impl FontBook {
|
|||||||
self.infos.push(info);
|
self.infos.push(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An ordered iterator over all font families this loader knows and details
|
/// An ordered iterator over all font families this book knows and details
|
||||||
/// about the faces that are part of them.
|
/// about the fonts that are part of them.
|
||||||
pub fn families(
|
pub fn families(
|
||||||
&self,
|
&self,
|
||||||
) -> impl Iterator<Item = (&str, impl Iterator<Item = &FontInfo>)> + '_ {
|
) -> impl Iterator<Item = (&str, impl Iterator<Item = &FontInfo>)> + '_ {
|
||||||
|
@ -15,7 +15,7 @@ use rex::font::MathHeader;
|
|||||||
use ttf_parser::{GlyphId, Tag};
|
use ttf_parser::{GlyphId, Tag};
|
||||||
|
|
||||||
use crate::geom::Em;
|
use crate::geom::Em;
|
||||||
use crate::loading::Buffer;
|
use crate::util::Buffer;
|
||||||
|
|
||||||
/// An OpenType font.
|
/// An OpenType font.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use crate::loading::Buffer;
|
use crate::util::Buffer;
|
||||||
|
|
||||||
/// A raster or vector image.
|
/// A raster or vector image.
|
||||||
///
|
///
|
||||||
|
128
src/lib.rs
128
src/lib.rs
@ -44,117 +44,75 @@ pub mod font;
|
|||||||
pub mod frame;
|
pub mod frame;
|
||||||
pub mod image;
|
pub mod image;
|
||||||
pub mod library;
|
pub mod library;
|
||||||
pub mod loading;
|
|
||||||
pub mod model;
|
pub mod model;
|
||||||
pub mod parse;
|
pub mod parse;
|
||||||
pub mod source;
|
pub mod source;
|
||||||
pub mod syntax;
|
pub mod syntax;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::io;
|
||||||
use std::sync::Arc;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use crate::diag::TypResult;
|
use crate::diag::TypResult;
|
||||||
use crate::eval::Scope;
|
use crate::eval::Scope;
|
||||||
|
use crate::font::{Font, FontBook};
|
||||||
use crate::frame::Frame;
|
use crate::frame::Frame;
|
||||||
use crate::loading::Loader;
|
|
||||||
use crate::model::StyleMap;
|
use crate::model::StyleMap;
|
||||||
use crate::source::{SourceId, SourceStore};
|
use crate::source::{Source, SourceId};
|
||||||
|
use crate::util::Buffer;
|
||||||
|
|
||||||
/// Typeset a source file into a collection of layouted frames.
|
/// Typeset a source file into a collection of layouted frames.
|
||||||
///
|
///
|
||||||
/// Returns either a vector of frames representing individual pages or
|
/// Returns either a vector of frames representing individual pages or
|
||||||
/// diagnostics in the form of a vector of error message with file and span
|
/// diagnostics in the form of a vector of error message with file and span
|
||||||
/// information.
|
/// information.
|
||||||
pub fn typeset(ctx: &mut Context, id: SourceId) -> TypResult<Vec<Frame>> {
|
pub fn typeset(world: &dyn World, main: SourceId) -> TypResult<Vec<Frame>> {
|
||||||
let module = eval::evaluate(ctx, id, vec![])?;
|
let module = eval::evaluate(world, main, vec![])?;
|
||||||
model::layout(ctx, &module.content)
|
model::layout(world, &module.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The core context which holds the configuration and stores.
|
/// The environment in which typesetting occurs.
|
||||||
pub struct Context {
|
pub trait World {
|
||||||
/// The loader for fonts and files.
|
/// Access the global configuration.
|
||||||
pub loader: Arc<dyn Loader>,
|
fn config(&self) -> &Config;
|
||||||
/// Stores loaded source files.
|
|
||||||
pub sources: SourceStore,
|
/// Resolve the unique id of a source file.
|
||||||
/// The context's configuration.
|
fn resolve(&self, path: &Path) -> io::Result<SourceId>;
|
||||||
config: Config,
|
|
||||||
|
/// Access a source file by id.
|
||||||
|
fn source(&self, id: SourceId) -> &Source;
|
||||||
|
|
||||||
|
/// Metadata about all known fonts.
|
||||||
|
fn book(&self) -> &FontBook;
|
||||||
|
|
||||||
|
/// Access the font with the given id.
|
||||||
|
fn font(&self, id: usize) -> io::Result<Font>;
|
||||||
|
|
||||||
|
/// Access a file at a path.
|
||||||
|
fn file(&self, path: &Path) -> io::Result<Buffer>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
/// The global configuration for typesetting.
|
||||||
/// Create a new context.
|
|
||||||
pub fn new(loader: Arc<dyn Loader>, config: Config) -> Self {
|
|
||||||
Self {
|
|
||||||
loader: Arc::clone(&loader),
|
|
||||||
sources: SourceStore::new(Arc::clone(&loader)),
|
|
||||||
config,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compilation configuration.
|
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// The compilation root.
|
/// The compilation root, relative to which absolute paths are.
|
||||||
|
///
|
||||||
|
/// Default: Empty path.
|
||||||
pub root: PathBuf,
|
pub root: PathBuf,
|
||||||
/// The standard library scope.
|
/// The scope containing definitions that are available everywhere.
|
||||||
pub std: Arc<Scope>,
|
///
|
||||||
/// The default styles.
|
/// Default: Typst's standard library.
|
||||||
pub styles: Arc<StyleMap>,
|
pub std: Scope,
|
||||||
}
|
/// The default properties for page size, font selection and so on.
|
||||||
|
///
|
||||||
impl Config {
|
/// Default: Empty style map.
|
||||||
/// Create a new configuration builder.
|
pub styles: StyleMap,
|
||||||
pub fn builder() -> ConfigBuilder {
|
|
||||||
ConfigBuilder::default()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::builder().build()
|
Self {
|
||||||
}
|
root: PathBuf::new(),
|
||||||
}
|
std: library::new(),
|
||||||
|
styles: StyleMap::new(),
|
||||||
/// A builder for a [`Config`].
|
|
||||||
///
|
|
||||||
/// This struct is created by [`Config::builder`].
|
|
||||||
#[derive(Debug, Default, Clone)]
|
|
||||||
pub struct ConfigBuilder {
|
|
||||||
root: PathBuf,
|
|
||||||
std: Option<Arc<Scope>>,
|
|
||||||
styles: Option<Arc<StyleMap>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConfigBuilder {
|
|
||||||
/// The compilation root, relative to which absolute paths are.
|
|
||||||
///
|
|
||||||
/// Default: Empty path.
|
|
||||||
pub fn root(&mut self, root: impl Into<PathBuf>) -> &mut Self {
|
|
||||||
self.root = root.into();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The scope containing definitions that are available everywhere.
|
|
||||||
///
|
|
||||||
/// Default: Typst's standard library.
|
|
||||||
pub fn std(&mut self, std: impl Into<Arc<Scope>>) -> &mut Self {
|
|
||||||
self.std = Some(std.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The default properties for page size, font selection and so on.
|
|
||||||
///
|
|
||||||
/// Default: Empty style map.
|
|
||||||
pub fn styles(&mut self, styles: impl Into<Arc<StyleMap>>) -> &mut Self {
|
|
||||||
self.styles = Some(styles.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finish building the configuration.
|
|
||||||
pub fn build(&self) -> Config {
|
|
||||||
Config {
|
|
||||||
root: self.root.clone(),
|
|
||||||
std: self.std.clone().unwrap_or_else(|| Arc::new(library::new())),
|
|
||||||
styles: self.styles.clone().unwrap_or_default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ pub struct HideNode(pub LayoutNode);
|
|||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl HideNode {
|
impl HideNode {
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
Ok(Content::inline(Self(args.expect("body")?)))
|
Ok(Content::inline(Self(args.expect("body")?)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -14,11 +14,11 @@ impl HideNode {
|
|||||||
impl Layout for HideNode {
|
impl Layout for HideNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
let mut frames = self.0.layout(ctx, regions, styles)?;
|
let mut frames = self.0.layout(world, regions, styles)?;
|
||||||
for frame in &mut frames {
|
for frame in &mut frames {
|
||||||
frame.clear();
|
frame.clear();
|
||||||
}
|
}
|
||||||
|
@ -13,15 +13,14 @@ impl ImageNode {
|
|||||||
/// How the image should adjust itself to a given area.
|
/// How the image should adjust itself to a given area.
|
||||||
pub const FIT: ImageFit = ImageFit::Cover;
|
pub const FIT: ImageFit = ImageFit::Cover;
|
||||||
|
|
||||||
fn construct(vm: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(vm: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
let Spanned { v: path, span } =
|
let Spanned { v: path, span } =
|
||||||
args.expect::<Spanned<EcoString>>("path to image file")?;
|
args.expect::<Spanned<EcoString>>("path to image file")?;
|
||||||
|
|
||||||
let full = vm.locate(&path).at(span)?;
|
let full = vm.locate(&path).at(span)?;
|
||||||
let ext = full.extension().and_then(OsStr::to_str).unwrap_or_default();
|
let ext = full.extension().and_then(OsStr::to_str).unwrap_or_default();
|
||||||
let image = vm
|
let image = vm
|
||||||
.ctx
|
.world
|
||||||
.loader
|
|
||||||
.file(&full)
|
.file(&full)
|
||||||
.and_then(|buffer| Image::new(buffer, ext))
|
.and_then(|buffer| Image::new(buffer, ext))
|
||||||
.map_err(|err| failed_to_load("image", &full, err))
|
.map_err(|err| failed_to_load("image", &full, err))
|
||||||
@ -39,7 +38,7 @@ impl ImageNode {
|
|||||||
impl Layout for ImageNode {
|
impl Layout for ImageNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
_: &mut Context,
|
_: &dyn World,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
|
@ -15,7 +15,7 @@ impl LineNode {
|
|||||||
#[property(resolve, fold)]
|
#[property(resolve, fold)]
|
||||||
pub const STROKE: RawStroke = RawStroke::default();
|
pub const STROKE: RawStroke = RawStroke::default();
|
||||||
|
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
let origin = args.named("origin")?.unwrap_or_default();
|
let origin = args.named("origin")?.unwrap_or_default();
|
||||||
|
|
||||||
let delta = match args.named::<Spec<Relative<RawLength>>>("to")? {
|
let delta = match args.named::<Spec<Relative<RawLength>>>("to")? {
|
||||||
@ -40,7 +40,7 @@ impl LineNode {
|
|||||||
impl Layout for LineNode {
|
impl Layout for LineNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
_: &mut Context,
|
_: &dyn World,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
|
@ -39,7 +39,7 @@ impl<const S: ShapeKind> ShapeNode<S> {
|
|||||||
pub const RADIUS: Corners<Option<Relative<RawLength>>> =
|
pub const RADIUS: Corners<Option<Relative<RawLength>>> =
|
||||||
Corners::splat(Relative::zero());
|
Corners::splat(Relative::zero());
|
||||||
|
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
let size = match S {
|
let size = match S {
|
||||||
SQUARE => args.named::<RawLength>("size")?.map(Relative::from),
|
SQUARE => args.named::<RawLength>("size")?.map(Relative::from),
|
||||||
CIRCLE => args.named::<RawLength>("radius")?.map(|r| 2.0 * Relative::from(r)),
|
CIRCLE => args.named::<RawLength>("radius")?.map(|r| 2.0 * Relative::from(r)),
|
||||||
@ -78,7 +78,7 @@ impl<const S: ShapeKind> ShapeNode<S> {
|
|||||||
impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
@ -93,7 +93,7 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
|||||||
let child = child.clone().padded(inset.map(|side| side.map(RawLength::from)));
|
let child = child.clone().padded(inset.map(|side| side.map(RawLength::from)));
|
||||||
|
|
||||||
let mut pod = Regions::one(regions.first, regions.base, regions.expand);
|
let mut pod = Regions::one(regions.first, regions.base, regions.expand);
|
||||||
frames = child.layout(ctx, &pod, styles)?;
|
frames = child.layout(world, &pod, styles)?;
|
||||||
|
|
||||||
for frame in frames.iter_mut() {
|
for frame in frames.iter_mut() {
|
||||||
frame.apply_role(Role::GenericBlock);
|
frame.apply_role(Role::GenericBlock);
|
||||||
@ -113,7 +113,7 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
|||||||
|
|
||||||
pod.first = Size::splat(length);
|
pod.first = Size::splat(length);
|
||||||
pod.expand = Spec::splat(true);
|
pod.expand = Spec::splat(true);
|
||||||
frames = child.layout(ctx, &pod, styles)?;
|
frames = child.layout(world, &pod, styles)?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// The default size that a shape takes on if it has no child and
|
// The default size that a shape takes on if it has no child and
|
||||||
|
@ -12,7 +12,7 @@ pub struct MoveNode {
|
|||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl MoveNode {
|
impl MoveNode {
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
let dx = args.named("dx")?.unwrap_or_default();
|
let dx = args.named("dx")?.unwrap_or_default();
|
||||||
let dy = args.named("dy")?.unwrap_or_default();
|
let dy = args.named("dy")?.unwrap_or_default();
|
||||||
Ok(Content::inline(Self {
|
Ok(Content::inline(Self {
|
||||||
@ -25,11 +25,11 @@ impl MoveNode {
|
|||||||
impl Layout for MoveNode {
|
impl Layout for MoveNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
let mut frames = self.child.layout(ctx, regions, styles)?;
|
let mut frames = self.child.layout(world, regions, styles)?;
|
||||||
|
|
||||||
let delta = self.delta.resolve(styles);
|
let delta = self.delta.resolve(styles);
|
||||||
for frame in &mut frames {
|
for frame in &mut frames {
|
||||||
@ -62,7 +62,7 @@ impl<const T: TransformKind> TransformNode<T> {
|
|||||||
#[property(resolve)]
|
#[property(resolve)]
|
||||||
pub const ORIGIN: Spec<Option<RawAlign>> = Spec::default();
|
pub const ORIGIN: Spec<Option<RawAlign>> = Spec::default();
|
||||||
|
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
let transform = match T {
|
let transform = match T {
|
||||||
ROTATE => {
|
ROTATE => {
|
||||||
let angle = args.named_or_find("angle")?.unwrap_or_default();
|
let angle = args.named_or_find("angle")?.unwrap_or_default();
|
||||||
@ -86,12 +86,12 @@ impl<const T: TransformKind> TransformNode<T> {
|
|||||||
impl<const T: TransformKind> Layout for TransformNode<T> {
|
impl<const T: TransformKind> Layout for TransformNode<T> {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON);
|
let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON);
|
||||||
let mut frames = self.child.layout(ctx, regions, styles)?;
|
let mut frames = self.child.layout(world, regions, styles)?;
|
||||||
|
|
||||||
for frame in &mut frames {
|
for frame in &mut frames {
|
||||||
let Spec { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
|
let Spec { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
|
||||||
|
@ -12,7 +12,7 @@ pub struct AlignNode {
|
|||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl AlignNode {
|
impl AlignNode {
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
let aligns: Spec<Option<RawAlign>> = args.find()?.unwrap_or_default();
|
let aligns: Spec<Option<RawAlign>> = args.find()?.unwrap_or_default();
|
||||||
let body: Content = args.expect("body")?;
|
let body: Content = args.expect("body")?;
|
||||||
Ok(match (body, aligns) {
|
Ok(match (body, aligns) {
|
||||||
@ -28,7 +28,7 @@ impl AlignNode {
|
|||||||
impl Layout for AlignNode {
|
impl Layout for AlignNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
@ -43,7 +43,7 @@ impl Layout for AlignNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Layout the child.
|
// Layout the child.
|
||||||
let mut frames = self.child.layout(ctx, &pod, passed.chain(&styles))?;
|
let mut frames = self.child.layout(world, &pod, passed.chain(&styles))?;
|
||||||
for (region, frame) in regions.iter().zip(&mut frames) {
|
for (region, frame) in regions.iter().zip(&mut frames) {
|
||||||
// Align in the target size. The target size depends on whether we
|
// Align in the target size. The target size depends on whether we
|
||||||
// should expand.
|
// should expand.
|
||||||
|
@ -17,7 +17,7 @@ impl ColumnsNode {
|
|||||||
#[property(resolve)]
|
#[property(resolve)]
|
||||||
pub const GUTTER: Relative<RawLength> = Ratio::new(0.04).into();
|
pub const GUTTER: Relative<RawLength> = Ratio::new(0.04).into();
|
||||||
|
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
Ok(Content::block(Self {
|
Ok(Content::block(Self {
|
||||||
columns: args.expect("column count")?,
|
columns: args.expect("column count")?,
|
||||||
child: args.expect("body")?,
|
child: args.expect("body")?,
|
||||||
@ -28,14 +28,14 @@ impl ColumnsNode {
|
|||||||
impl Layout for ColumnsNode {
|
impl Layout for ColumnsNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
// Separating the infinite space into infinite columns does not make
|
// Separating the infinite space into infinite columns does not make
|
||||||
// much sense.
|
// much sense.
|
||||||
if !regions.first.x.is_finite() {
|
if !regions.first.x.is_finite() {
|
||||||
return self.child.layout(ctx, regions, styles);
|
return self.child.layout(world, regions, styles);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the width of the gutter and each column.
|
// Determine the width of the gutter and each column.
|
||||||
@ -57,7 +57,7 @@ impl Layout for ColumnsNode {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Layout the children.
|
// Layout the children.
|
||||||
let mut frames = self.child.layout(ctx, &pod, styles)?.into_iter();
|
let mut frames = self.child.layout(world, &pod, styles)?.into_iter();
|
||||||
let mut finished = vec![];
|
let mut finished = vec![];
|
||||||
|
|
||||||
let dir = styles.get(TextNode::DIR);
|
let dir = styles.get(TextNode::DIR);
|
||||||
@ -106,7 +106,7 @@ pub struct ColbreakNode;
|
|||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl ColbreakNode {
|
impl ColbreakNode {
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
let weak = args.named("weak")?.unwrap_or(false);
|
let weak = args.named("weak")?.unwrap_or(false);
|
||||||
Ok(Content::Colbreak { weak })
|
Ok(Content::Colbreak { weak })
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ pub struct BoxNode;
|
|||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl BoxNode {
|
impl BoxNode {
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
let width = args.named("width")?;
|
let width = args.named("width")?;
|
||||||
let height = args.named("height")?;
|
let height = args.named("height")?;
|
||||||
let body: LayoutNode = args.eat()?.unwrap_or_default();
|
let body: LayoutNode = args.eat()?.unwrap_or_default();
|
||||||
@ -18,7 +18,7 @@ pub struct BlockNode;
|
|||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl BlockNode {
|
impl BlockNode {
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
Ok(Content::Block(args.eat()?.unwrap_or_default()))
|
Ok(Content::Block(args.eat()?.unwrap_or_default()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ pub enum FlowChild {
|
|||||||
impl Layout for FlowNode {
|
impl Layout for FlowNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
@ -38,7 +38,7 @@ impl Layout for FlowNode {
|
|||||||
layouter.layout_spacing(*kind, styles);
|
layouter.layout_spacing(*kind, styles);
|
||||||
}
|
}
|
||||||
FlowChild::Node(ref node) => {
|
FlowChild::Node(ref node) => {
|
||||||
layouter.layout_node(ctx, node, styles)?;
|
layouter.layout_node(world, node, styles)?;
|
||||||
}
|
}
|
||||||
FlowChild::Colbreak => {
|
FlowChild::Colbreak => {
|
||||||
layouter.finish_region();
|
layouter.finish_region();
|
||||||
@ -149,7 +149,7 @@ impl FlowLayouter {
|
|||||||
/// Layout a node.
|
/// Layout a node.
|
||||||
pub fn layout_node(
|
pub fn layout_node(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
node: &LayoutNode,
|
node: &LayoutNode,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<()> {
|
) -> TypResult<()> {
|
||||||
@ -162,7 +162,7 @@ impl FlowLayouter {
|
|||||||
// aligned later.
|
// aligned later.
|
||||||
if let Some(placed) = node.downcast::<PlaceNode>() {
|
if let Some(placed) = node.downcast::<PlaceNode>() {
|
||||||
if placed.out_of_flow() {
|
if placed.out_of_flow() {
|
||||||
let frame = node.layout(ctx, &self.regions, styles)?.remove(0);
|
let frame = node.layout(world, &self.regions, styles)?.remove(0);
|
||||||
self.items.push(FlowItem::Placed(frame));
|
self.items.push(FlowItem::Placed(frame));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -180,7 +180,7 @@ impl FlowLayouter {
|
|||||||
.unwrap_or(Align::Top),
|
.unwrap_or(Align::Top),
|
||||||
);
|
);
|
||||||
|
|
||||||
let frames = node.layout(ctx, &self.regions, styles)?;
|
let frames = node.layout(world, &self.regions, styles)?;
|
||||||
let len = frames.len();
|
let len = frames.len();
|
||||||
for (i, mut frame) in frames.into_iter().enumerate() {
|
for (i, mut frame) in frames.into_iter().enumerate() {
|
||||||
// Set the generic block role.
|
// Set the generic block role.
|
||||||
|
@ -13,7 +13,7 @@ pub struct GridNode {
|
|||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl GridNode {
|
impl GridNode {
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
let columns = args.named("columns")?.unwrap_or_default();
|
let columns = args.named("columns")?.unwrap_or_default();
|
||||||
let rows = args.named("rows")?.unwrap_or_default();
|
let rows = args.named("rows")?.unwrap_or_default();
|
||||||
let base_gutter: Vec<TrackSizing> = args.named("gutter")?.unwrap_or_default();
|
let base_gutter: Vec<TrackSizing> = args.named("gutter")?.unwrap_or_default();
|
||||||
@ -33,13 +33,13 @@ impl GridNode {
|
|||||||
impl Layout for GridNode {
|
impl Layout for GridNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
// Prepare grid layout by unifying content and gutter tracks.
|
// Prepare grid layout by unifying content and gutter tracks.
|
||||||
let layouter = GridLayouter::new(
|
let layouter = GridLayouter::new(
|
||||||
ctx,
|
world,
|
||||||
self.tracks.as_deref(),
|
self.tracks.as_deref(),
|
||||||
self.gutter.as_deref(),
|
self.gutter.as_deref(),
|
||||||
&self.cells,
|
&self.cells,
|
||||||
@ -93,7 +93,7 @@ castable! {
|
|||||||
/// Performs grid layout.
|
/// Performs grid layout.
|
||||||
pub struct GridLayouter<'a> {
|
pub struct GridLayouter<'a> {
|
||||||
/// The core context.
|
/// The core context.
|
||||||
ctx: &'a mut Context,
|
world: &'a dyn World,
|
||||||
/// The grid cells.
|
/// The grid cells.
|
||||||
cells: &'a [LayoutNode],
|
cells: &'a [LayoutNode],
|
||||||
/// The column tracks including gutter tracks.
|
/// The column tracks including gutter tracks.
|
||||||
@ -133,7 +133,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
///
|
///
|
||||||
/// This prepares grid layout by unifying content and gutter tracks.
|
/// This prepares grid layout by unifying content and gutter tracks.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
ctx: &'a mut Context,
|
world: &'a dyn World,
|
||||||
tracks: Spec<&[TrackSizing]>,
|
tracks: Spec<&[TrackSizing]>,
|
||||||
gutter: Spec<&[TrackSizing]>,
|
gutter: Spec<&[TrackSizing]>,
|
||||||
cells: &'a [LayoutNode],
|
cells: &'a [LayoutNode],
|
||||||
@ -187,7 +187,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
regions.expand = Spec::new(true, false);
|
regions.expand = Spec::new(true, false);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
ctx,
|
world,
|
||||||
cells,
|
cells,
|
||||||
cols,
|
cols,
|
||||||
rows,
|
rows,
|
||||||
@ -301,7 +301,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
v.resolve(self.styles).relative_to(self.regions.base.y);
|
v.resolve(self.styles).relative_to(self.regions.base.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
let frame = node.layout(self.ctx, &pod, self.styles)?.remove(0);
|
let frame = node.layout(self.world, &pod, self.styles)?.remove(0);
|
||||||
resolved.set_max(frame.width());
|
resolved.set_max(frame.width());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -371,7 +371,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut sizes = node
|
let mut sizes = node
|
||||||
.layout(self.ctx, &pod, self.styles)?
|
.layout(self.world, &pod, self.styles)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|frame| frame.height());
|
.map(|frame| frame.height());
|
||||||
|
|
||||||
@ -460,7 +460,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
.select(self.regions.base, size);
|
.select(self.regions.base, size);
|
||||||
|
|
||||||
let pod = Regions::one(size, base, Spec::splat(true));
|
let pod = Regions::one(size, base, Spec::splat(true));
|
||||||
let frame = node.layout(self.ctx, &pod, self.styles)?.remove(0);
|
let frame = node.layout(self.world, &pod, self.styles)?.remove(0);
|
||||||
match frame.role() {
|
match frame.role() {
|
||||||
Some(Role::ListLabel | Role::ListItemBody) => {
|
Some(Role::ListLabel | Role::ListItemBody) => {
|
||||||
output.apply_role(Role::ListItem)
|
output.apply_role(Role::ListItem)
|
||||||
@ -508,7 +508,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Push the layouted frames into the individual output frames.
|
// Push the layouted frames into the individual output frames.
|
||||||
let frames = node.layout(self.ctx, &pod, self.styles)?;
|
let frames = node.layout(self.world, &pod, self.styles)?;
|
||||||
for (output, frame) in outputs.iter_mut().zip(frames) {
|
for (output, frame) in outputs.iter_mut().zip(frames) {
|
||||||
match frame.role() {
|
match frame.role() {
|
||||||
Some(Role::ListLabel | Role::ListItemBody) => {
|
Some(Role::ListLabel | Role::ListItemBody) => {
|
||||||
|
@ -11,7 +11,7 @@ pub struct PadNode {
|
|||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl PadNode {
|
impl PadNode {
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
let all = args.named("rest")?.or(args.find()?);
|
let all = args.named("rest")?.or(args.find()?);
|
||||||
let x = args.named("x")?;
|
let x = args.named("x")?;
|
||||||
let y = args.named("y")?;
|
let y = args.named("y")?;
|
||||||
@ -28,14 +28,14 @@ impl PadNode {
|
|||||||
impl Layout for PadNode {
|
impl Layout for PadNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
// Layout child into padded regions.
|
// Layout child into padded regions.
|
||||||
let padding = self.padding.resolve(styles);
|
let padding = self.padding.resolve(styles);
|
||||||
let pod = regions.map(|size| shrink(size, padding));
|
let pod = regions.map(|size| shrink(size, padding));
|
||||||
let mut frames = self.child.layout(ctx, &pod, styles)?;
|
let mut frames = self.child.layout(world, &pod, styles)?;
|
||||||
|
|
||||||
for frame in &mut frames {
|
for frame in &mut frames {
|
||||||
// Apply the padding inversely such that the grown size padded
|
// Apply the padding inversely such that the grown size padded
|
||||||
|
@ -41,7 +41,7 @@ impl PageNode {
|
|||||||
#[property(referenced)]
|
#[property(referenced)]
|
||||||
pub const FOREGROUND: Marginal = Marginal::None;
|
pub const FOREGROUND: Marginal = Marginal::None;
|
||||||
|
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
Ok(Content::Page(Self(args.expect("body")?)))
|
Ok(Content::Page(Self(args.expect("body")?)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ impl PageNode {
|
|||||||
/// Layout the page run into a sequence of frames, one per page.
|
/// Layout the page run into a sequence of frames, one per page.
|
||||||
pub fn layout(
|
pub fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
mut page: usize,
|
mut page: usize,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
@ -97,7 +97,7 @@ impl PageNode {
|
|||||||
|
|
||||||
// Layout the child.
|
// Layout the child.
|
||||||
let regions = Regions::repeat(size, size, size.map(Length::is_finite));
|
let regions = Regions::repeat(size, size, size.map(Length::is_finite));
|
||||||
let mut frames = child.layout(ctx, ®ions, styles)?;
|
let mut frames = child.layout(world, ®ions, styles)?;
|
||||||
|
|
||||||
let header = styles.get(Self::HEADER);
|
let header = styles.get(Self::HEADER);
|
||||||
let footer = styles.get(Self::FOOTER);
|
let footer = styles.get(Self::FOOTER);
|
||||||
@ -126,9 +126,9 @@ impl PageNode {
|
|||||||
(Role::Foreground, foreground, Point::zero(), size),
|
(Role::Foreground, foreground, Point::zero(), size),
|
||||||
(Role::Background, background, Point::zero(), size),
|
(Role::Background, background, Point::zero(), size),
|
||||||
] {
|
] {
|
||||||
if let Some(content) = marginal.resolve(ctx, page)? {
|
if let Some(content) = marginal.resolve(world, page)? {
|
||||||
let pod = Regions::one(area, area, Spec::splat(true));
|
let pod = Regions::one(area, area, Spec::splat(true));
|
||||||
let mut sub = content.layout(ctx, &pod, styles)?.remove(0);
|
let mut sub = content.layout(world, &pod, styles)?.remove(0);
|
||||||
sub.apply_role(role);
|
sub.apply_role(role);
|
||||||
|
|
||||||
if role == Role::Background {
|
if role == Role::Background {
|
||||||
@ -159,7 +159,7 @@ pub struct PagebreakNode;
|
|||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl PagebreakNode {
|
impl PagebreakNode {
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
let weak = args.named("weak")?.unwrap_or(false);
|
let weak = args.named("weak")?.unwrap_or(false);
|
||||||
Ok(Content::Pagebreak { weak })
|
Ok(Content::Pagebreak { weak })
|
||||||
}
|
}
|
||||||
@ -178,13 +178,13 @@ pub enum Marginal {
|
|||||||
|
|
||||||
impl Marginal {
|
impl Marginal {
|
||||||
/// Resolve the marginal based on the page number.
|
/// Resolve the marginal based on the page number.
|
||||||
pub fn resolve(&self, ctx: &mut Context, page: usize) -> TypResult<Option<Content>> {
|
pub fn resolve(&self, world: &dyn World, page: usize) -> TypResult<Option<Content>> {
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
Self::None => None,
|
Self::None => None,
|
||||||
Self::Content(content) => Some(content.clone()),
|
Self::Content(content) => Some(content.clone()),
|
||||||
Self::Func(func, span) => {
|
Self::Func(func, span) => {
|
||||||
let args = Args::new(*span, [Value::Int(page as i64)]);
|
let args = Args::new(*span, [Value::Int(page as i64)]);
|
||||||
Some(func.call_detached(ctx, args)?.display())
|
Some(func.call_detached(world, args)?.display())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ pub struct PlaceNode(pub LayoutNode);
|
|||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl PlaceNode {
|
impl PlaceNode {
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
let aligns = args.find()?.unwrap_or(Spec::with_x(Some(RawAlign::Start)));
|
let aligns = args.find()?.unwrap_or(Spec::with_x(Some(RawAlign::Start)));
|
||||||
let dx = args.named("dx")?.unwrap_or_default();
|
let dx = args.named("dx")?.unwrap_or_default();
|
||||||
let dy = args.named("dy")?.unwrap_or_default();
|
let dy = args.named("dy")?.unwrap_or_default();
|
||||||
@ -21,7 +21,7 @@ impl PlaceNode {
|
|||||||
impl Layout for PlaceNode {
|
impl Layout for PlaceNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
@ -35,7 +35,7 @@ impl Layout for PlaceNode {
|
|||||||
Regions::one(regions.base, regions.base, expand)
|
Regions::one(regions.base, regions.base, expand)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut frames = self.0.layout(ctx, &pod, styles)?;
|
let mut frames = self.0.layout(world, &pod, styles)?;
|
||||||
|
|
||||||
// If expansion is off, zero all sizes so that we don't take up any
|
// If expansion is off, zero all sizes so that we don't take up any
|
||||||
// space in our parent. Otherwise, respect the expand settings.
|
// space in our parent. Otherwise, respect the expand settings.
|
||||||
|
@ -8,7 +8,7 @@ pub struct HNode;
|
|||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl HNode {
|
impl HNode {
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
let amount = args.expect("spacing")?;
|
let amount = args.expect("spacing")?;
|
||||||
let weak = args.named("weak")?.unwrap_or(false);
|
let weak = args.named("weak")?.unwrap_or(false);
|
||||||
Ok(Content::Horizontal { amount, weak })
|
Ok(Content::Horizontal { amount, weak })
|
||||||
@ -20,7 +20,7 @@ pub struct VNode;
|
|||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl VNode {
|
impl VNode {
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
let amount = args.expect("spacing")?;
|
let amount = args.expect("spacing")?;
|
||||||
let weak = args.named("weak")?.unwrap_or(false);
|
let weak = args.named("weak")?.unwrap_or(false);
|
||||||
Ok(Content::Vertical { amount, weak, generated: false })
|
Ok(Content::Vertical { amount, weak, generated: false })
|
||||||
|
@ -15,7 +15,7 @@ pub struct StackNode {
|
|||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl StackNode {
|
impl StackNode {
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
Ok(Content::block(Self {
|
Ok(Content::block(Self {
|
||||||
dir: args.named("dir")?.unwrap_or(Dir::TTB),
|
dir: args.named("dir")?.unwrap_or(Dir::TTB),
|
||||||
spacing: args.named("spacing")?,
|
spacing: args.named("spacing")?,
|
||||||
@ -27,7 +27,7 @@ impl StackNode {
|
|||||||
impl Layout for StackNode {
|
impl Layout for StackNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
@ -47,7 +47,7 @@ impl Layout for StackNode {
|
|||||||
layouter.layout_spacing(kind);
|
layouter.layout_spacing(kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
layouter.layout_node(ctx, node, styles)?;
|
layouter.layout_node(world, node, styles)?;
|
||||||
deferred = self.spacing;
|
deferred = self.spacing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -168,7 +168,7 @@ impl<'a> StackLayouter<'a> {
|
|||||||
/// Layout an arbitrary node.
|
/// Layout an arbitrary node.
|
||||||
pub fn layout_node(
|
pub fn layout_node(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
node: &LayoutNode,
|
node: &LayoutNode,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<()> {
|
) -> TypResult<()> {
|
||||||
@ -193,7 +193,7 @@ impl<'a> StackLayouter<'a> {
|
|||||||
self.dir.start().into()
|
self.dir.start().into()
|
||||||
});
|
});
|
||||||
|
|
||||||
let frames = node.layout(ctx, &self.regions, styles)?;
|
let frames = node.layout(world, &self.regions, styles)?;
|
||||||
let len = frames.len();
|
let len = frames.len();
|
||||||
for (i, mut frame) in frames.into_iter().enumerate() {
|
for (i, mut frame) in frames.into_iter().enumerate() {
|
||||||
// Set the generic block role.
|
// Set the generic block role.
|
||||||
|
@ -28,7 +28,7 @@ impl MathNode {
|
|||||||
#[property(resolve, shorthand(around))]
|
#[property(resolve, shorthand(around))]
|
||||||
pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into());
|
pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into());
|
||||||
|
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
Ok(Content::show(Self {
|
Ok(Content::show(Self {
|
||||||
formula: args.expect("formula")?,
|
formula: args.expect("formula")?,
|
||||||
display: args.named("display")?.unwrap_or(false),
|
display: args.named("display")?.unwrap_or(false),
|
||||||
@ -48,7 +48,7 @@ impl Show for MathNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, _: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
fn realize(&self, _: &dyn World, styles: StyleChain) -> TypResult<Content> {
|
||||||
let node = self::rex::RexNode {
|
let node = self::rex::RexNode {
|
||||||
tex: self.formula.clone(),
|
tex: self.formula.clone(),
|
||||||
display: self.display,
|
display: self.display,
|
||||||
@ -64,7 +64,7 @@ impl Show for MathNode {
|
|||||||
|
|
||||||
fn finalize(
|
fn finalize(
|
||||||
&self,
|
&self,
|
||||||
_: &mut Context,
|
_: &dyn World,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
mut realized: Content,
|
mut realized: Content,
|
||||||
) -> TypResult<Content> {
|
) -> TypResult<Content> {
|
||||||
|
@ -22,17 +22,16 @@ pub struct RexNode {
|
|||||||
impl Layout for RexNode {
|
impl Layout for RexNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
_: &Regions,
|
_: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
// Load the font.
|
// Load the font.
|
||||||
let span = self.tex.span;
|
let span = self.tex.span;
|
||||||
let font = ctx
|
let font = world
|
||||||
.loader
|
|
||||||
.book()
|
.book()
|
||||||
.select(self.family.as_str(), variant(styles))
|
.select(self.family.as_str(), variant(styles))
|
||||||
.and_then(|id| ctx.loader.font(id).ok())
|
.and_then(|id| world.font(id).ok())
|
||||||
.ok_or("failed to find math font")
|
.ok_or("failed to find math font")
|
||||||
.at(span)?;
|
.at(span)?;
|
||||||
|
|
||||||
|
@ -12,16 +12,15 @@ pub use crate::diag::{
|
|||||||
failed_to_load, with_alternative, At, Error, StrResult, TypError, TypResult,
|
failed_to_load, with_alternative, At, Error, StrResult, TypError, TypResult,
|
||||||
};
|
};
|
||||||
pub use crate::eval::{
|
pub use crate::eval::{
|
||||||
Arg, Args, Array, Cast, Dict, Dynamic, Func, Machine, Node, RawAlign, RawLength,
|
Arg, Args, Array, Cast, Dict, Dynamic, Func, Node, RawAlign, RawLength, RawStroke,
|
||||||
RawStroke, Scope, Smart, Value,
|
Scope, Smart, Value, Vm,
|
||||||
};
|
};
|
||||||
pub use crate::frame::*;
|
pub use crate::frame::*;
|
||||||
pub use crate::geom::*;
|
pub use crate::geom::*;
|
||||||
pub use crate::loading::Loader;
|
|
||||||
pub use crate::model::{
|
pub use crate::model::{
|
||||||
Content, Fold, Key, Layout, LayoutNode, Regions, Resolve, Selector, Show, ShowNode,
|
Content, Fold, Key, Layout, LayoutNode, Regions, Resolve, Selector, Show, ShowNode,
|
||||||
StyleChain, StyleMap, StyleVec,
|
StyleChain, StyleMap, StyleVec,
|
||||||
};
|
};
|
||||||
pub use crate::syntax::{Span, Spanned};
|
pub use crate::syntax::{Span, Spanned};
|
||||||
pub use crate::util::EcoString;
|
pub use crate::util::EcoString;
|
||||||
pub use crate::Context;
|
pub use crate::World;
|
||||||
|
@ -7,11 +7,11 @@ pub struct DocNode(pub StyleVec<PageNode>);
|
|||||||
|
|
||||||
impl DocNode {
|
impl DocNode {
|
||||||
/// Layout the document into a sequence of frames, one per page.
|
/// Layout the document into a sequence of frames, one per page.
|
||||||
pub fn layout(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Vec<Frame>> {
|
pub fn layout(&self, world: &dyn World, styles: StyleChain) -> TypResult<Vec<Frame>> {
|
||||||
let mut frames = vec![];
|
let mut frames = vec![];
|
||||||
for (page, map) in self.0.iter() {
|
for (page, map) in self.0.iter() {
|
||||||
let number = 1 + frames.len();
|
let number = 1 + frames.len();
|
||||||
frames.extend(page.layout(ctx, number, map.chain(&styles))?);
|
frames.extend(page.layout(world, number, map.chain(&styles))?);
|
||||||
}
|
}
|
||||||
Ok(frames)
|
Ok(frames)
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ impl HeadingNode {
|
|||||||
/// Whether the heading is numbered.
|
/// Whether the heading is numbered.
|
||||||
pub const NUMBERED: bool = true;
|
pub const NUMBERED: bool = true;
|
||||||
|
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
Ok(Content::show(Self {
|
Ok(Content::show(Self {
|
||||||
body: args.expect("body")?,
|
body: args.expect("body")?,
|
||||||
level: args.named("level")?.unwrap_or(NonZeroUsize::new(1).unwrap()),
|
level: args.named("level")?.unwrap_or(NonZeroUsize::new(1).unwrap()),
|
||||||
@ -82,19 +82,19 @@ impl Show for HeadingNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, _: &mut Context, _: StyleChain) -> TypResult<Content> {
|
fn realize(&self, _: &dyn World, _: StyleChain) -> TypResult<Content> {
|
||||||
Ok(Content::block(self.body.clone()))
|
Ok(Content::block(self.body.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finalize(
|
fn finalize(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
mut realized: Content,
|
mut realized: Content,
|
||||||
) -> TypResult<Content> {
|
) -> TypResult<Content> {
|
||||||
macro_rules! resolve {
|
macro_rules! resolve {
|
||||||
($key:expr) => {
|
($key:expr) => {
|
||||||
styles.get($key).resolve(ctx, self.level)?
|
styles.get($key).resolve(world, self.level)?
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,13 +149,13 @@ pub enum Leveled<T> {
|
|||||||
|
|
||||||
impl<T: Cast + Clone> Leveled<T> {
|
impl<T: Cast + Clone> Leveled<T> {
|
||||||
/// Resolve the value based on the level.
|
/// Resolve the value based on the level.
|
||||||
pub fn resolve(&self, ctx: &mut Context, level: NonZeroUsize) -> TypResult<T> {
|
pub fn resolve(&self, world: &dyn World, level: NonZeroUsize) -> TypResult<T> {
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
Self::Value(value) => value.clone(),
|
Self::Value(value) => value.clone(),
|
||||||
Self::Mapping(mapping) => mapping(level),
|
Self::Mapping(mapping) => mapping(level),
|
||||||
Self::Func(func, span) => {
|
Self::Func(func, span) => {
|
||||||
let args = Args::new(*span, [Value::Int(level.get() as i64)]);
|
let args = Args::new(*span, [Value::Int(level.get() as i64)]);
|
||||||
func.call_detached(ctx, args)?.cast().at(*span)?
|
func.call_detached(world, args)?.cast().at(*span)?
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ impl<const L: ListKind> ListNode<L> {
|
|||||||
#[property(resolve)]
|
#[property(resolve)]
|
||||||
pub const SPACING: BlockSpacing = Ratio::one().into();
|
pub const SPACING: BlockSpacing = Ratio::one().into();
|
||||||
|
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
Ok(Content::show(Self {
|
Ok(Content::show(Self {
|
||||||
start: args.named("start")?.unwrap_or(1),
|
start: args.named("start")?.unwrap_or(1),
|
||||||
tight: args.named("tight")?.unwrap_or(true),
|
tight: args.named("tight")?.unwrap_or(true),
|
||||||
@ -100,7 +100,7 @@ impl<const L: ListKind> Show for ListNode<L> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
fn realize(&self, world: &dyn World, styles: StyleChain) -> TypResult<Content> {
|
||||||
let mut cells = vec![];
|
let mut cells = vec![];
|
||||||
let mut number = self.start;
|
let mut number = self.start;
|
||||||
|
|
||||||
@ -112,7 +112,7 @@ impl<const L: ListKind> Show for ListNode<L> {
|
|||||||
cells.push(LayoutNode::default());
|
cells.push(LayoutNode::default());
|
||||||
cells.push(
|
cells.push(
|
||||||
label
|
label
|
||||||
.resolve(ctx, L, number)?
|
.resolve(world, L, number)?
|
||||||
.styled_with_map(map.clone())
|
.styled_with_map(map.clone())
|
||||||
.role(Role::ListLabel)
|
.role(Role::ListLabel)
|
||||||
.pack(),
|
.pack(),
|
||||||
@ -145,7 +145,7 @@ impl<const L: ListKind> Show for ListNode<L> {
|
|||||||
|
|
||||||
fn finalize(
|
fn finalize(
|
||||||
&self,
|
&self,
|
||||||
_: &mut Context,
|
_: &dyn World,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
realized: Content,
|
realized: Content,
|
||||||
) -> TypResult<Content> {
|
) -> TypResult<Content> {
|
||||||
@ -208,7 +208,7 @@ impl Label {
|
|||||||
/// Resolve the value based on the level.
|
/// Resolve the value based on the level.
|
||||||
pub fn resolve(
|
pub fn resolve(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
kind: ListKind,
|
kind: ListKind,
|
||||||
number: usize,
|
number: usize,
|
||||||
) -> TypResult<Content> {
|
) -> TypResult<Content> {
|
||||||
@ -225,7 +225,7 @@ impl Label {
|
|||||||
Self::Content(content) => content.clone(),
|
Self::Content(content) => content.clone(),
|
||||||
Self::Func(func, span) => {
|
Self::Func(func, span) => {
|
||||||
let args = Args::new(*span, [Value::Int(number as i64)]);
|
let args = Args::new(*span, [Value::Int(number as i64)]);
|
||||||
func.call_detached(ctx, args)?.display()
|
func.call_detached(world, args)?.display()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ pub struct RefNode(pub EcoString);
|
|||||||
|
|
||||||
#[node(showable)]
|
#[node(showable)]
|
||||||
impl RefNode {
|
impl RefNode {
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
Ok(Content::show(Self(args.expect("label")?)))
|
Ok(Content::show(Self(args.expect("label")?)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -22,7 +22,7 @@ impl Show for RefNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, _: &mut Context, _: StyleChain) -> TypResult<Content> {
|
fn realize(&self, _: &dyn World, _: StyleChain) -> TypResult<Content> {
|
||||||
Ok(Content::Text(format_eco!("@{}", self.0)))
|
Ok(Content::Text(format_eco!("@{}", self.0)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ impl TableNode {
|
|||||||
#[property(resolve, shorthand(around))]
|
#[property(resolve, shorthand(around))]
|
||||||
pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into());
|
pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into());
|
||||||
|
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
let columns = args.named("columns")?.unwrap_or_default();
|
let columns = args.named("columns")?.unwrap_or_default();
|
||||||
let rows = args.named("rows")?.unwrap_or_default();
|
let rows = args.named("rows")?.unwrap_or_default();
|
||||||
let base_gutter: Vec<TrackSizing> = args.named("gutter")?.unwrap_or_default();
|
let base_gutter: Vec<TrackSizing> = args.named("gutter")?.unwrap_or_default();
|
||||||
@ -72,7 +72,7 @@ impl Show for TableNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
fn realize(&self, world: &dyn World, styles: StyleChain) -> TypResult<Content> {
|
||||||
let fill = styles.get(Self::FILL);
|
let fill = styles.get(Self::FILL);
|
||||||
let stroke = styles.get(Self::STROKE).map(RawStroke::unwrap_or_default);
|
let stroke = styles.get(Self::STROKE).map(RawStroke::unwrap_or_default);
|
||||||
let padding = styles.get(Self::PADDING);
|
let padding = styles.get(Self::PADDING);
|
||||||
@ -92,7 +92,7 @@ impl Show for TableNode {
|
|||||||
|
|
||||||
let x = i % cols;
|
let x = i % cols;
|
||||||
let y = i / cols;
|
let y = i / cols;
|
||||||
if let Some(fill) = fill.resolve(ctx, x, y)? {
|
if let Some(fill) = fill.resolve(world, x, y)? {
|
||||||
child = child.filled(fill);
|
child = child.filled(fill);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +110,7 @@ impl Show for TableNode {
|
|||||||
|
|
||||||
fn finalize(
|
fn finalize(
|
||||||
&self,
|
&self,
|
||||||
_: &mut Context,
|
_: &dyn World,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
realized: Content,
|
realized: Content,
|
||||||
) -> TypResult<Content> {
|
) -> TypResult<Content> {
|
||||||
@ -129,12 +129,12 @@ pub enum Celled<T> {
|
|||||||
|
|
||||||
impl<T: Cast + Clone> Celled<T> {
|
impl<T: Cast + Clone> Celled<T> {
|
||||||
/// Resolve the value based on the cell position.
|
/// Resolve the value based on the cell position.
|
||||||
pub fn resolve(&self, ctx: &mut Context, x: usize, y: usize) -> TypResult<T> {
|
pub fn resolve(&self, world: &dyn World, x: usize, y: usize) -> TypResult<T> {
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
Self::Value(value) => value.clone(),
|
Self::Value(value) => value.clone(),
|
||||||
Self::Func(func, span) => {
|
Self::Func(func, span) => {
|
||||||
let args = Args::new(*span, [Value::Int(x as i64), Value::Int(y as i64)]);
|
let args = Args::new(*span, [Value::Int(x as i64), Value::Int(y as i64)]);
|
||||||
func.call_detached(ctx, args)?.cast().at(*span)?
|
func.call_detached(world, args)?.cast().at(*span)?
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ impl<const L: DecoLine> DecoNode<L> {
|
|||||||
/// with the glyphs. Does not apply to strikethrough.
|
/// with the glyphs. Does not apply to strikethrough.
|
||||||
pub const EVADE: bool = true;
|
pub const EVADE: bool = true;
|
||||||
|
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
Ok(Content::show(Self(args.expect("body")?)))
|
Ok(Content::show(Self(args.expect("body")?)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,7 +48,7 @@ impl<const L: DecoLine> Show for DecoNode<L> {
|
|||||||
dict! { "body" => Value::Content(self.0.clone()) }
|
dict! { "body" => Value::Content(self.0.clone()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, _: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
fn realize(&self, _: &dyn World, styles: StyleChain) -> TypResult<Content> {
|
||||||
Ok(self.0.clone().styled(TextNode::DECO, Decoration {
|
Ok(self.0.clone().styled(TextNode::DECO, Decoration {
|
||||||
line: L,
|
line: L,
|
||||||
stroke: styles.get(Self::STROKE).unwrap_or_default(),
|
stroke: styles.get(Self::STROKE).unwrap_or_default(),
|
||||||
|
@ -18,7 +18,7 @@ impl LinkNode {
|
|||||||
/// Whether to underline the link.
|
/// Whether to underline the link.
|
||||||
pub const UNDERLINE: Smart<bool> = Smart::Auto;
|
pub const UNDERLINE: Smart<bool> = Smart::Auto;
|
||||||
|
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
Ok(Content::show({
|
Ok(Content::show({
|
||||||
let dest = args.expect::<Destination>("destination")?;
|
let dest = args.expect::<Destination>("destination")?;
|
||||||
let body = match dest {
|
let body = match dest {
|
||||||
@ -64,7 +64,7 @@ impl Show for LinkNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, _: &mut Context, _: StyleChain) -> TypResult<Content> {
|
fn realize(&self, _: &dyn World, _: StyleChain) -> TypResult<Content> {
|
||||||
Ok(self.body.clone().unwrap_or_else(|| match &self.dest {
|
Ok(self.body.clone().unwrap_or_else(|| match &self.dest {
|
||||||
Destination::Url(url) => {
|
Destination::Url(url) => {
|
||||||
let mut text = url.as_str();
|
let mut text = url.as_str();
|
||||||
@ -80,7 +80,7 @@ impl Show for LinkNode {
|
|||||||
|
|
||||||
fn finalize(
|
fn finalize(
|
||||||
&self,
|
&self,
|
||||||
_: &mut Context,
|
_: &dyn World,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
mut realized: Content,
|
mut realized: Content,
|
||||||
) -> TypResult<Content> {
|
) -> TypResult<Content> {
|
||||||
|
@ -128,7 +128,7 @@ impl TextNode {
|
|||||||
#[property(skip, fold)]
|
#[property(skip, fold)]
|
||||||
pub const DECO: Decoration = vec![];
|
pub const DECO: Decoration = vec![];
|
||||||
|
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
// The text constructor is special: It doesn't create a text node.
|
// The text constructor is special: It doesn't create a text node.
|
||||||
// Instead, it leaves the passed argument structurally unchanged, but
|
// Instead, it leaves the passed argument structurally unchanged, but
|
||||||
// styles all text in it.
|
// styles all text in it.
|
||||||
@ -422,12 +422,12 @@ impl Fold for Vec<(Tag, u32)> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a string or content to lowercase.
|
/// Convert a string or content to lowercase.
|
||||||
pub fn lower(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn lower(_: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
case(Case::Lower, args)
|
case(Case::Lower, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a string or content to uppercase.
|
/// Convert a string or content to uppercase.
|
||||||
pub fn upper(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn upper(_: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
case(Case::Upper, args)
|
case(Case::Upper, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -461,7 +461,7 @@ impl Case {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Display text in small capitals.
|
/// Display text in small capitals.
|
||||||
pub fn smallcaps(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn smallcaps(_: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
let body: Content = args.expect("content")?;
|
let body: Content = args.expect("content")?;
|
||||||
Ok(Value::Content(body.styled(TextNode::SMALLCAPS, true)))
|
Ok(Value::Content(body.styled(TextNode::SMALLCAPS, true)))
|
||||||
}
|
}
|
||||||
@ -493,7 +493,7 @@ pub struct StrongNode(pub Content);
|
|||||||
|
|
||||||
#[node(showable)]
|
#[node(showable)]
|
||||||
impl StrongNode {
|
impl StrongNode {
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
Ok(Content::show(Self(args.expect("body")?)))
|
Ok(Content::show(Self(args.expect("body")?)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -507,7 +507,7 @@ impl Show for StrongNode {
|
|||||||
dict! { "body" => Value::Content(self.0.clone()) }
|
dict! { "body" => Value::Content(self.0.clone()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, _: &mut Context, _: StyleChain) -> TypResult<Content> {
|
fn realize(&self, _: &dyn World, _: StyleChain) -> TypResult<Content> {
|
||||||
Ok(self.0.clone().styled(TextNode::BOLD, Toggle))
|
Ok(self.0.clone().styled(TextNode::BOLD, Toggle))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -518,7 +518,7 @@ pub struct EmphNode(pub Content);
|
|||||||
|
|
||||||
#[node(showable)]
|
#[node(showable)]
|
||||||
impl EmphNode {
|
impl EmphNode {
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
Ok(Content::show(Self(args.expect("body")?)))
|
Ok(Content::show(Self(args.expect("body")?)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -532,7 +532,7 @@ impl Show for EmphNode {
|
|||||||
dict! { "body" => Value::Content(self.0.clone()) }
|
dict! { "body" => Value::Content(self.0.clone()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, _: &mut Context, _: StyleChain) -> TypResult<Content> {
|
fn realize(&self, _: &dyn World, _: StyleChain) -> TypResult<Content> {
|
||||||
Ok(self.0.clone().styled(TextNode::ITALIC, Toggle))
|
Ok(self.0.clone().styled(TextNode::ITALIC, Toggle))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ impl ParNode {
|
|||||||
#[property(resolve)]
|
#[property(resolve)]
|
||||||
pub const LINEBREAKS: Smart<Linebreaks> = Smart::Auto;
|
pub const LINEBREAKS: Smart<Linebreaks> = Smart::Auto;
|
||||||
|
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
// The paragraph constructor is special: It doesn't create a paragraph
|
// The paragraph constructor is special: It doesn't create a paragraph
|
||||||
// node. Instead, it just ensures that the passed content lives is in a
|
// node. Instead, it just ensures that the passed content lives is in a
|
||||||
// separate paragraph and styles it.
|
// separate paragraph and styles it.
|
||||||
@ -64,7 +64,7 @@ impl ParNode {
|
|||||||
impl Layout for ParNode {
|
impl Layout for ParNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
@ -74,13 +74,13 @@ impl Layout for ParNode {
|
|||||||
// Perform BiDi analysis and then prepare paragraph layout by building a
|
// Perform BiDi analysis and then prepare paragraph layout by building a
|
||||||
// representation on which we can do line breaking without layouting
|
// representation on which we can do line breaking without layouting
|
||||||
// each and every line from scratch.
|
// each and every line from scratch.
|
||||||
let p = prepare(ctx, self, &text, segments, regions, styles)?;
|
let p = prepare(world, self, &text, segments, regions, styles)?;
|
||||||
|
|
||||||
// Break the paragraph into lines.
|
// Break the paragraph into lines.
|
||||||
let lines = linebreak(&p, ctx.loader.as_ref(), regions.first.x);
|
let lines = linebreak(&p, world, regions.first.x);
|
||||||
|
|
||||||
// Stack the lines into one frame per region.
|
// Stack the lines into one frame per region.
|
||||||
stack(&p, ctx, &lines, regions)
|
stack(&p, world, &lines, regions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,7 +170,7 @@ pub struct ParbreakNode;
|
|||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl ParbreakNode {
|
impl ParbreakNode {
|
||||||
fn construct(_: &mut Machine, _: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, _: &mut Args) -> TypResult<Content> {
|
||||||
Ok(Content::Parbreak)
|
Ok(Content::Parbreak)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -180,7 +180,7 @@ pub struct LinebreakNode;
|
|||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl LinebreakNode {
|
impl LinebreakNode {
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
let justified = args.named("justified")?.unwrap_or(false);
|
let justified = args.named("justified")?.unwrap_or(false);
|
||||||
Ok(Content::Linebreak { justified })
|
Ok(Content::Linebreak { justified })
|
||||||
}
|
}
|
||||||
@ -496,7 +496,7 @@ fn collect<'a>(
|
|||||||
/// Prepare paragraph layout by shaping the whole paragraph and layouting all
|
/// Prepare paragraph layout by shaping the whole paragraph and layouting all
|
||||||
/// contained inline-level nodes.
|
/// contained inline-level nodes.
|
||||||
fn prepare<'a>(
|
fn prepare<'a>(
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
par: &'a ParNode,
|
par: &'a ParNode,
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
segments: Vec<(Segment<'a>, StyleChain<'a>)>,
|
segments: Vec<(Segment<'a>, StyleChain<'a>)>,
|
||||||
@ -517,13 +517,7 @@ fn prepare<'a>(
|
|||||||
let end = cursor + segment.len();
|
let end = cursor + segment.len();
|
||||||
match segment {
|
match segment {
|
||||||
Segment::Text(_) => {
|
Segment::Text(_) => {
|
||||||
shape_range(
|
shape_range(&mut items, world, &bidi, cursor .. end, styles);
|
||||||
&mut items,
|
|
||||||
ctx.loader.as_ref(),
|
|
||||||
&bidi,
|
|
||||||
cursor .. end,
|
|
||||||
styles,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Segment::Spacing(spacing) => match spacing {
|
Segment::Spacing(spacing) => match spacing {
|
||||||
Spacing::Relative(v) => {
|
Spacing::Relative(v) => {
|
||||||
@ -540,7 +534,7 @@ fn prepare<'a>(
|
|||||||
} else {
|
} else {
|
||||||
let size = Size::new(regions.first.x, regions.base.y);
|
let size = Size::new(regions.first.x, regions.base.y);
|
||||||
let pod = Regions::one(size, regions.base, Spec::splat(false));
|
let pod = Regions::one(size, regions.base, Spec::splat(false));
|
||||||
let mut frame = node.layout(ctx, &pod, styles)?.remove(0);
|
let mut frame = node.layout(world, &pod, styles)?.remove(0);
|
||||||
frame.translate(Point::with_y(styles.get(TextNode::BASELINE)));
|
frame.translate(Point::with_y(styles.get(TextNode::BASELINE)));
|
||||||
frame.apply_role(Role::GenericInline);
|
frame.apply_role(Role::GenericInline);
|
||||||
items.push(Item::Frame(frame));
|
items.push(Item::Frame(frame));
|
||||||
@ -567,14 +561,14 @@ fn prepare<'a>(
|
|||||||
/// items for them.
|
/// items for them.
|
||||||
fn shape_range<'a>(
|
fn shape_range<'a>(
|
||||||
items: &mut Vec<Item<'a>>,
|
items: &mut Vec<Item<'a>>,
|
||||||
loader: &dyn Loader,
|
world: &dyn World,
|
||||||
bidi: &BidiInfo<'a>,
|
bidi: &BidiInfo<'a>,
|
||||||
range: Range,
|
range: Range,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
) {
|
) {
|
||||||
let mut process = |text, level: Level| {
|
let mut process = |text, level: Level| {
|
||||||
let dir = if level.is_ltr() { Dir::LTR } else { Dir::RTL };
|
let dir = if level.is_ltr() { Dir::LTR } else { Dir::RTL };
|
||||||
let shaped = shape(loader, text, styles, dir);
|
let shaped = shape(world, text, styles, dir);
|
||||||
items.push(Item::Text(shaped));
|
items.push(Item::Text(shaped));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -633,12 +627,12 @@ fn shared_get<'a, K: Key<'a>>(
|
|||||||
/// Find suitable linebreaks.
|
/// Find suitable linebreaks.
|
||||||
fn linebreak<'a>(
|
fn linebreak<'a>(
|
||||||
p: &'a Preparation<'a>,
|
p: &'a Preparation<'a>,
|
||||||
loader: &dyn Loader,
|
world: &dyn World,
|
||||||
width: Length,
|
width: Length,
|
||||||
) -> Vec<Line<'a>> {
|
) -> Vec<Line<'a>> {
|
||||||
match p.styles.get(ParNode::LINEBREAKS) {
|
match p.styles.get(ParNode::LINEBREAKS) {
|
||||||
Linebreaks::Simple => linebreak_simple(p, loader, width),
|
Linebreaks::Simple => linebreak_simple(p, world, width),
|
||||||
Linebreaks::Optimized => linebreak_optimized(p, loader, width),
|
Linebreaks::Optimized => linebreak_optimized(p, world, width),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -647,7 +641,7 @@ fn linebreak<'a>(
|
|||||||
/// very unbalanced line, but is fast and simple.
|
/// very unbalanced line, but is fast and simple.
|
||||||
fn linebreak_simple<'a>(
|
fn linebreak_simple<'a>(
|
||||||
p: &'a Preparation<'a>,
|
p: &'a Preparation<'a>,
|
||||||
loader: &dyn Loader,
|
world: &dyn World,
|
||||||
width: Length,
|
width: Length,
|
||||||
) -> Vec<Line<'a>> {
|
) -> Vec<Line<'a>> {
|
||||||
let mut lines = vec![];
|
let mut lines = vec![];
|
||||||
@ -656,7 +650,7 @@ fn linebreak_simple<'a>(
|
|||||||
|
|
||||||
for (end, mandatory, hyphen) in breakpoints(p) {
|
for (end, mandatory, hyphen) in breakpoints(p) {
|
||||||
// Compute the line and its size.
|
// Compute the line and its size.
|
||||||
let mut attempt = line(p, loader, start .. end, mandatory, hyphen);
|
let mut attempt = line(p, world, start .. end, mandatory, hyphen);
|
||||||
|
|
||||||
// If the line doesn't fit anymore, we push the last fitting attempt
|
// If the line doesn't fit anymore, we push the last fitting attempt
|
||||||
// into the stack and rebuild the line from the attempt's end. The
|
// into the stack and rebuild the line from the attempt's end. The
|
||||||
@ -665,7 +659,7 @@ fn linebreak_simple<'a>(
|
|||||||
if let Some((last_attempt, last_end)) = last.take() {
|
if let Some((last_attempt, last_end)) = last.take() {
|
||||||
lines.push(last_attempt);
|
lines.push(last_attempt);
|
||||||
start = last_end;
|
start = last_end;
|
||||||
attempt = line(p, loader, start .. end, mandatory, hyphen);
|
attempt = line(p, world, start .. end, mandatory, hyphen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -707,7 +701,7 @@ fn linebreak_simple<'a>(
|
|||||||
/// text.
|
/// text.
|
||||||
fn linebreak_optimized<'a>(
|
fn linebreak_optimized<'a>(
|
||||||
p: &'a Preparation<'a>,
|
p: &'a Preparation<'a>,
|
||||||
loader: &dyn Loader,
|
world: &dyn World,
|
||||||
width: Length,
|
width: Length,
|
||||||
) -> Vec<Line<'a>> {
|
) -> Vec<Line<'a>> {
|
||||||
/// The cost of a line or paragraph layout.
|
/// The cost of a line or paragraph layout.
|
||||||
@ -732,7 +726,7 @@ fn linebreak_optimized<'a>(
|
|||||||
let mut table = vec![Entry {
|
let mut table = vec![Entry {
|
||||||
pred: 0,
|
pred: 0,
|
||||||
total: 0.0,
|
total: 0.0,
|
||||||
line: line(p, loader, 0 .. 0, false, false),
|
line: line(p, world, 0 .. 0, false, false),
|
||||||
}];
|
}];
|
||||||
|
|
||||||
let em = p.styles.get(TextNode::SIZE);
|
let em = p.styles.get(TextNode::SIZE);
|
||||||
@ -746,7 +740,7 @@ fn linebreak_optimized<'a>(
|
|||||||
for (i, pred) in table.iter_mut().enumerate().skip(active) {
|
for (i, pred) in table.iter_mut().enumerate().skip(active) {
|
||||||
// Layout the line.
|
// Layout the line.
|
||||||
let start = pred.line.end;
|
let start = pred.line.end;
|
||||||
let attempt = line(p, loader, start .. end, mandatory, hyphen);
|
let attempt = line(p, world, start .. end, mandatory, hyphen);
|
||||||
|
|
||||||
// Determine how much the line's spaces would need to be stretched
|
// Determine how much the line's spaces would need to be stretched
|
||||||
// to make it the desired width.
|
// to make it the desired width.
|
||||||
@ -920,7 +914,7 @@ impl Breakpoints<'_> {
|
|||||||
/// Create a line which spans the given range.
|
/// Create a line which spans the given range.
|
||||||
fn line<'a>(
|
fn line<'a>(
|
||||||
p: &'a Preparation,
|
p: &'a Preparation,
|
||||||
loader: &dyn Loader,
|
world: &dyn World,
|
||||||
mut range: Range,
|
mut range: Range,
|
||||||
mandatory: bool,
|
mandatory: bool,
|
||||||
hyphen: bool,
|
hyphen: bool,
|
||||||
@ -975,9 +969,9 @@ fn line<'a>(
|
|||||||
if hyphen || start + shaped.text.len() > range.end {
|
if hyphen || start + shaped.text.len() > range.end {
|
||||||
if hyphen || start < range.end || before.is_empty() {
|
if hyphen || start < range.end || before.is_empty() {
|
||||||
let shifted = start - base .. range.end - base;
|
let shifted = start - base .. range.end - base;
|
||||||
let mut reshaped = shaped.reshape(loader, shifted);
|
let mut reshaped = shaped.reshape(world, shifted);
|
||||||
if hyphen || shy {
|
if hyphen || shy {
|
||||||
reshaped.push_hyphen(loader);
|
reshaped.push_hyphen(world);
|
||||||
}
|
}
|
||||||
width += reshaped.width;
|
width += reshaped.width;
|
||||||
last = Some(Item::Text(reshaped));
|
last = Some(Item::Text(reshaped));
|
||||||
@ -998,7 +992,7 @@ fn line<'a>(
|
|||||||
if range.start + shaped.text.len() > end {
|
if range.start + shaped.text.len() > end {
|
||||||
if range.start < end {
|
if range.start < end {
|
||||||
let shifted = range.start - base .. end - base;
|
let shifted = range.start - base .. end - base;
|
||||||
let reshaped = shaped.reshape(loader, shifted);
|
let reshaped = shaped.reshape(world, shifted);
|
||||||
width += reshaped.width;
|
width += reshaped.width;
|
||||||
first = Some(Item::Text(reshaped));
|
first = Some(Item::Text(reshaped));
|
||||||
}
|
}
|
||||||
@ -1028,7 +1022,7 @@ fn line<'a>(
|
|||||||
/// Combine layouted lines into one frame per region.
|
/// Combine layouted lines into one frame per region.
|
||||||
fn stack(
|
fn stack(
|
||||||
p: &Preparation,
|
p: &Preparation,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
lines: &[Line],
|
lines: &[Line],
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
@ -1048,7 +1042,7 @@ fn stack(
|
|||||||
|
|
||||||
// Stack the lines into one frame per region.
|
// Stack the lines into one frame per region.
|
||||||
for line in lines {
|
for line in lines {
|
||||||
let frame = commit(p, ctx, line, ®ions, width)?;
|
let frame = commit(p, world, line, ®ions, width)?;
|
||||||
let height = frame.size().y;
|
let height = frame.size().y;
|
||||||
|
|
||||||
while !regions.first.y.fits(height) && !regions.in_last() {
|
while !regions.first.y.fits(height) && !regions.in_last() {
|
||||||
@ -1078,7 +1072,7 @@ fn stack(
|
|||||||
/// Commit to a line and build its frame.
|
/// Commit to a line and build its frame.
|
||||||
fn commit(
|
fn commit(
|
||||||
p: &Preparation,
|
p: &Preparation,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
line: &Line,
|
line: &Line,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
width: Length,
|
width: Length,
|
||||||
@ -1149,7 +1143,7 @@ fn commit(
|
|||||||
offset += v.share(fr, remaining);
|
offset += v.share(fr, remaining);
|
||||||
}
|
}
|
||||||
Item::Text(shaped) => {
|
Item::Text(shaped) => {
|
||||||
let frame = shaped.build(ctx.loader.as_ref(), justification);
|
let frame = shaped.build(world, justification);
|
||||||
push(&mut offset, frame);
|
push(&mut offset, frame);
|
||||||
}
|
}
|
||||||
Item::Frame(frame) => {
|
Item::Frame(frame) => {
|
||||||
@ -1160,7 +1154,7 @@ fn commit(
|
|||||||
let fill = Fraction::one().share(fr, remaining);
|
let fill = Fraction::one().share(fr, remaining);
|
||||||
let size = Size::new(fill, regions.base.y);
|
let size = Size::new(fill, regions.base.y);
|
||||||
let pod = Regions::one(size, regions.base, Spec::new(false, false));
|
let pod = Regions::one(size, regions.base, Spec::new(false, false));
|
||||||
let frame = node.layout(ctx, &pod, *styles)?.remove(0);
|
let frame = node.layout(world, &pod, *styles)?.remove(0);
|
||||||
let width = frame.width();
|
let width = frame.width();
|
||||||
let count = (fill / width).floor();
|
let count = (fill / width).floor();
|
||||||
let remaining = fill % width;
|
let remaining = fill % width;
|
||||||
|
@ -35,7 +35,7 @@ impl RawNode {
|
|||||||
#[property(resolve, shorthand(around))]
|
#[property(resolve, shorthand(around))]
|
||||||
pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into());
|
pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into());
|
||||||
|
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
Ok(Content::show(Self {
|
Ok(Content::show(Self {
|
||||||
text: args.expect("text")?,
|
text: args.expect("text")?,
|
||||||
block: args.named("block")?.unwrap_or(false),
|
block: args.named("block")?.unwrap_or(false),
|
||||||
@ -59,7 +59,7 @@ impl Show for RawNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, _: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
fn realize(&self, _: &dyn World, styles: StyleChain) -> TypResult<Content> {
|
||||||
let lang = styles.get(Self::LANG).as_ref().map(|s| s.to_lowercase());
|
let lang = styles.get(Self::LANG).as_ref().map(|s| s.to_lowercase());
|
||||||
let foreground = THEME
|
let foreground = THEME
|
||||||
.settings
|
.settings
|
||||||
@ -111,7 +111,7 @@ impl Show for RawNode {
|
|||||||
|
|
||||||
fn finalize(
|
fn finalize(
|
||||||
&self,
|
&self,
|
||||||
_: &mut Context,
|
_: &dyn World,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
mut realized: Content,
|
mut realized: Content,
|
||||||
) -> TypResult<Content> {
|
) -> TypResult<Content> {
|
||||||
|
@ -6,7 +6,7 @@ pub struct RepeatNode(pub LayoutNode);
|
|||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl RepeatNode {
|
impl RepeatNode {
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
Ok(Content::inline(Self(args.expect("body")?)))
|
Ok(Content::inline(Self(args.expect("body")?)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -14,11 +14,11 @@ impl RepeatNode {
|
|||||||
impl Layout for RepeatNode {
|
impl Layout for RepeatNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
// The actual repeating happens directly in the paragraph.
|
// The actual repeating happens directly in the paragraph.
|
||||||
self.0.layout(ctx, regions, styles)
|
self.0.layout(world, regions, styles)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,8 +80,8 @@ impl<'a> ShapedText<'a> {
|
|||||||
///
|
///
|
||||||
/// The `justification` defines how much extra advance width each
|
/// The `justification` defines how much extra advance width each
|
||||||
/// [justifiable glyph](ShapedGlyph::is_justifiable) will get.
|
/// [justifiable glyph](ShapedGlyph::is_justifiable) will get.
|
||||||
pub fn build(&self, loader: &dyn Loader, justification: Length) -> Frame {
|
pub fn build(&self, world: &dyn World, justification: Length) -> Frame {
|
||||||
let (top, bottom) = self.measure(loader);
|
let (top, bottom) = self.measure(world);
|
||||||
let size = Size::new(self.width, top + bottom);
|
let size = Size::new(self.width, top + bottom);
|
||||||
|
|
||||||
let mut offset = Length::zero();
|
let mut offset = Length::zero();
|
||||||
@ -144,7 +144,7 @@ impl<'a> ShapedText<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Measure the top and bottom extent of this text.
|
/// Measure the top and bottom extent of this text.
|
||||||
fn measure(&self, loader: &dyn Loader) -> (Length, Length) {
|
fn measure(&self, world: &dyn World) -> (Length, Length) {
|
||||||
let mut top = Length::zero();
|
let mut top = Length::zero();
|
||||||
let mut bottom = Length::zero();
|
let mut bottom = Length::zero();
|
||||||
|
|
||||||
@ -162,10 +162,10 @@ impl<'a> ShapedText<'a> {
|
|||||||
// When there are no glyphs, we just use the vertical metrics of the
|
// When there are no glyphs, we just use the vertical metrics of the
|
||||||
// first available font.
|
// first available font.
|
||||||
for family in families(self.styles) {
|
for family in families(self.styles) {
|
||||||
if let Some(font) = loader
|
if let Some(font) = world
|
||||||
.book()
|
.book()
|
||||||
.select(family, self.variant)
|
.select(family, self.variant)
|
||||||
.and_then(|id| loader.font(id).ok())
|
.and_then(|id| world.font(id).ok())
|
||||||
{
|
{
|
||||||
expand(&font);
|
expand(&font);
|
||||||
break;
|
break;
|
||||||
@ -199,7 +199,7 @@ impl<'a> ShapedText<'a> {
|
|||||||
/// shaping process if possible.
|
/// shaping process if possible.
|
||||||
pub fn reshape(
|
pub fn reshape(
|
||||||
&'a self,
|
&'a self,
|
||||||
loader: &dyn Loader,
|
world: &dyn World,
|
||||||
text_range: Range<usize>,
|
text_range: Range<usize>,
|
||||||
) -> ShapedText<'a> {
|
) -> ShapedText<'a> {
|
||||||
if let Some(glyphs) = self.slice_safe_to_break(text_range.clone()) {
|
if let Some(glyphs) = self.slice_safe_to_break(text_range.clone()) {
|
||||||
@ -213,17 +213,17 @@ impl<'a> ShapedText<'a> {
|
|||||||
glyphs: Cow::Borrowed(glyphs),
|
glyphs: Cow::Borrowed(glyphs),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
shape(loader, &self.text[text_range], self.styles, self.dir)
|
shape(world, &self.text[text_range], self.styles, self.dir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push a hyphen to end of the text.
|
/// Push a hyphen to end of the text.
|
||||||
pub fn push_hyphen(&mut self, loader: &dyn Loader) {
|
pub fn push_hyphen(&mut self, world: &dyn World) {
|
||||||
families(self.styles).find_map(|family| {
|
families(self.styles).find_map(|family| {
|
||||||
let font = loader
|
let font = world
|
||||||
.book()
|
.book()
|
||||||
.select(family, self.variant)
|
.select(family, self.variant)
|
||||||
.and_then(|id| loader.font(id).ok())?;
|
.and_then(|id| world.font(id).ok())?;
|
||||||
let ttf = font.ttf();
|
let ttf = font.ttf();
|
||||||
let glyph_id = ttf.glyph_index('-')?;
|
let glyph_id = ttf.glyph_index('-')?;
|
||||||
let x_advance = font.to_em(ttf.glyph_hor_advance(glyph_id)?);
|
let x_advance = font.to_em(ttf.glyph_hor_advance(glyph_id)?);
|
||||||
@ -306,7 +306,7 @@ impl Debug for ShapedText<'_> {
|
|||||||
|
|
||||||
/// Holds shaping results and metadata common to all shaped segments.
|
/// Holds shaping results and metadata common to all shaped segments.
|
||||||
struct ShapingContext<'a> {
|
struct ShapingContext<'a> {
|
||||||
loader: &'a dyn Loader,
|
world: &'a dyn World,
|
||||||
glyphs: Vec<ShapedGlyph>,
|
glyphs: Vec<ShapedGlyph>,
|
||||||
used: Vec<Font>,
|
used: Vec<Font>,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
@ -319,7 +319,7 @@ struct ShapingContext<'a> {
|
|||||||
|
|
||||||
/// Shape text into [`ShapedText`].
|
/// Shape text into [`ShapedText`].
|
||||||
pub fn shape<'a>(
|
pub fn shape<'a>(
|
||||||
loader: &dyn Loader,
|
world: &dyn World,
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
dir: Dir,
|
dir: Dir,
|
||||||
@ -327,7 +327,7 @@ pub fn shape<'a>(
|
|||||||
let size = styles.get(TextNode::SIZE);
|
let size = styles.get(TextNode::SIZE);
|
||||||
|
|
||||||
let mut ctx = ShapingContext {
|
let mut ctx = ShapingContext {
|
||||||
loader,
|
world,
|
||||||
size,
|
size,
|
||||||
glyphs: vec![],
|
glyphs: vec![],
|
||||||
used: vec![],
|
used: vec![],
|
||||||
@ -368,10 +368,10 @@ fn shape_segment<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Find the next available family.
|
// Find the next available family.
|
||||||
let book = ctx.loader.book();
|
let book = ctx.world.book();
|
||||||
let mut selection = families.find_map(|family| {
|
let mut selection = families.find_map(|family| {
|
||||||
book.select(family, ctx.variant)
|
book.select(family, ctx.variant)
|
||||||
.and_then(|id| ctx.loader.font(id).ok())
|
.and_then(|id| ctx.world.font(id).ok())
|
||||||
.filter(|font| !ctx.used.contains(font))
|
.filter(|font| !ctx.used.contains(font))
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -380,7 +380,7 @@ fn shape_segment<'a>(
|
|||||||
let first = ctx.used.first().map(Font::info);
|
let first = ctx.used.first().map(Font::info);
|
||||||
selection = book
|
selection = book
|
||||||
.select_fallback(first, ctx.variant, text)
|
.select_fallback(first, ctx.variant, text)
|
||||||
.and_then(|id| ctx.loader.font(id).ok())
|
.and_then(|id| ctx.world.font(id).ok())
|
||||||
.filter(|font| !ctx.used.contains(font));
|
.filter(|font| !ctx.used.contains(font));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ impl<const S: ScriptKind> ShiftNode<S> {
|
|||||||
/// The font size for synthetic sub- and superscripts.
|
/// The font size for synthetic sub- and superscripts.
|
||||||
pub const SIZE: TextSize = TextSize(Em::new(0.6).into());
|
pub const SIZE: TextSize = TextSize(Em::new(0.6).into());
|
||||||
|
|
||||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Content> {
|
||||||
Ok(Content::show(Self(args.expect("body")?)))
|
Ok(Content::show(Self(args.expect("body")?)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -42,11 +42,11 @@ impl<const S: ScriptKind> Show for ShiftNode<S> {
|
|||||||
dict! { "body" => Value::Content(self.0.clone()) }
|
dict! { "body" => Value::Content(self.0.clone()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
fn realize(&self, world: &dyn World, styles: StyleChain) -> TypResult<Content> {
|
||||||
let mut transformed = None;
|
let mut transformed = None;
|
||||||
if styles.get(Self::TYPOGRAPHIC) {
|
if styles.get(Self::TYPOGRAPHIC) {
|
||||||
if let Some(text) = search_text(&self.0, S) {
|
if let Some(text) = search_text(&self.0, S) {
|
||||||
if is_shapable(ctx.loader.as_ref(), &text, styles) {
|
if is_shapable(world, &text, styles) {
|
||||||
transformed = Some(Content::Text(text));
|
transformed = Some(Content::Text(text));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,12 +91,12 @@ fn search_text(content: &Content, mode: ScriptKind) -> Option<EcoString> {
|
|||||||
|
|
||||||
/// Checks whether the first retrievable family contains all code points of the
|
/// Checks whether the first retrievable family contains all code points of the
|
||||||
/// given string.
|
/// given string.
|
||||||
fn is_shapable(loader: &dyn Loader, text: &str, styles: StyleChain) -> bool {
|
fn is_shapable(world: &dyn World, text: &str, styles: StyleChain) -> bool {
|
||||||
let book = loader.book();
|
|
||||||
for family in styles.get(TextNode::FAMILY).iter() {
|
for family in styles.get(TextNode::FAMILY).iter() {
|
||||||
if let Some(font) = book
|
if let Some(font) = world
|
||||||
|
.book()
|
||||||
.select(family.as_str(), variant(styles))
|
.select(family.as_str(), variant(styles))
|
||||||
.and_then(|id| loader.font(id).ok())
|
.and_then(|id| world.font(id).ok())
|
||||||
{
|
{
|
||||||
return text.chars().all(|c| font.ttf().glyph_index(c).is_some());
|
return text.chars().all(|c| font.ttf().glyph_index(c).is_some());
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,13 @@ use std::str::FromStr;
|
|||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
|
|
||||||
/// Create a grayscale color.
|
/// Create a grayscale color.
|
||||||
pub fn luma(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn luma(_: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
let Component(luma) = args.expect("gray component")?;
|
let Component(luma) = args.expect("gray component")?;
|
||||||
Ok(Value::Color(LumaColor::new(luma).into()))
|
Ok(Value::Color(LumaColor::new(luma).into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create an RGB(A) color.
|
/// Create an RGB(A) color.
|
||||||
pub fn rgb(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn rgb(_: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
Ok(Value::Color(
|
Ok(Value::Color(
|
||||||
if let Some(string) = args.find::<Spanned<EcoString>>()? {
|
if let Some(string) = args.find::<Spanned<EcoString>>()? {
|
||||||
match RgbaColor::from_str(&string.v) {
|
match RgbaColor::from_str(&string.v) {
|
||||||
@ -27,7 +27,7 @@ pub fn rgb(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a CMYK color.
|
/// Create a CMYK color.
|
||||||
pub fn cmyk(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn cmyk(_: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
let RatioComponent(c) = args.expect("cyan component")?;
|
let RatioComponent(c) = args.expect("cyan component")?;
|
||||||
let RatioComponent(m) = args.expect("magenta component")?;
|
let RatioComponent(m) = args.expect("magenta component")?;
|
||||||
let RatioComponent(y) = args.expect("yellow component")?;
|
let RatioComponent(y) = args.expect("yellow component")?;
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
|
|
||||||
/// Read structured data from a CSV file.
|
/// Read structured data from a CSV file.
|
||||||
pub fn csv(vm: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn csv(vm: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
let Spanned { v: path, span } =
|
let Spanned { v: path, span } =
|
||||||
args.expect::<Spanned<EcoString>>("path to csv file")?;
|
args.expect::<Spanned<EcoString>>("path to csv file")?;
|
||||||
|
|
||||||
let path = vm.locate(&path).at(span)?;
|
let path = vm.locate(&path).at(span)?;
|
||||||
let try_load = || -> io::Result<Value> {
|
let try_load = || -> io::Result<Value> {
|
||||||
let data = vm.ctx.loader.file(&path)?;
|
let data = vm.world.file(&path)?;
|
||||||
|
|
||||||
let mut builder = csv::ReaderBuilder::new();
|
let mut builder = csv::ReaderBuilder::new();
|
||||||
builder.has_headers(false);
|
builder.has_headers(false);
|
||||||
|
@ -3,7 +3,7 @@ use std::cmp::Ordering;
|
|||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
|
|
||||||
/// Convert a value to an integer.
|
/// Convert a value to an integer.
|
||||||
pub fn int(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn int(_: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
let Spanned { v, span } = args.expect("value")?;
|
let Spanned { v, span } = args.expect("value")?;
|
||||||
Ok(Value::Int(match v {
|
Ok(Value::Int(match v {
|
||||||
Value::Bool(v) => v as i64,
|
Value::Bool(v) => v as i64,
|
||||||
@ -18,7 +18,7 @@ pub fn int(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a value to a float.
|
/// Convert a value to a float.
|
||||||
pub fn float(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn float(_: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
let Spanned { v, span } = args.expect("value")?;
|
let Spanned { v, span } = args.expect("value")?;
|
||||||
Ok(Value::Float(match v {
|
Ok(Value::Float(match v {
|
||||||
Value::Int(v) => v as f64,
|
Value::Int(v) => v as f64,
|
||||||
@ -32,7 +32,7 @@ pub fn float(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The absolute value of a numeric value.
|
/// The absolute value of a numeric value.
|
||||||
pub fn abs(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn abs(_: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
let Spanned { v, span } = args.expect("numeric value")?;
|
let Spanned { v, span } = args.expect("numeric value")?;
|
||||||
Ok(match v {
|
Ok(match v {
|
||||||
Value::Int(v) => Value::Int(v.abs()),
|
Value::Int(v) => Value::Int(v.abs()),
|
||||||
@ -48,12 +48,12 @@ pub fn abs(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The minimum of a sequence of values.
|
/// The minimum of a sequence of values.
|
||||||
pub fn min(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn min(_: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
minmax(args, Ordering::Less)
|
minmax(args, Ordering::Less)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The maximum of a sequence of values.
|
/// The maximum of a sequence of values.
|
||||||
pub fn max(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn max(_: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
minmax(args, Ordering::Greater)
|
minmax(args, Ordering::Greater)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,17 +79,17 @@ fn minmax(args: &mut Args, goal: Ordering) -> TypResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Whether an integer is even.
|
/// Whether an integer is even.
|
||||||
pub fn even(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn even(_: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
Ok(Value::Bool(args.expect::<i64>("integer")? % 2 == 0))
|
Ok(Value::Bool(args.expect::<i64>("integer")? % 2 == 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether an integer is odd.
|
/// Whether an integer is odd.
|
||||||
pub fn odd(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn odd(_: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
Ok(Value::Bool(args.expect::<i64>("integer")? % 2 != 0))
|
Ok(Value::Bool(args.expect::<i64>("integer")? % 2 != 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The modulo of two numbers.
|
/// The modulo of two numbers.
|
||||||
pub fn mod_(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn mod_(_: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
let Spanned { v: v1, span: span1 } = args.expect("integer or float")?;
|
let Spanned { v: v1, span: span1 } = args.expect("integer or float")?;
|
||||||
let Spanned { v: v2, span: span2 } = args.expect("integer or float")?;
|
let Spanned { v: v2, span: span2 } = args.expect("integer or float")?;
|
||||||
|
|
||||||
@ -119,7 +119,7 @@ pub fn mod_(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a sequence of numbers.
|
/// Create a sequence of numbers.
|
||||||
pub fn range(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn range(_: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
let first = args.expect::<i64>("end")?;
|
let first = args.expect::<i64>("end")?;
|
||||||
let (start, end) = match args.eat::<i64>()? {
|
let (start, end) = match args.eat::<i64>()? {
|
||||||
Some(second) => (first, second),
|
Some(second) => (first, second),
|
||||||
|
@ -10,17 +10,17 @@ pub use data::*;
|
|||||||
pub use math::*;
|
pub use math::*;
|
||||||
pub use string::*;
|
pub use string::*;
|
||||||
|
|
||||||
use crate::eval::{Eval, Machine, Scopes};
|
use crate::eval::{Eval, Scopes, Vm};
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::source::Source;
|
use crate::source::Source;
|
||||||
|
|
||||||
/// The name of a value's type.
|
/// The name of a value's type.
|
||||||
pub fn type_(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn type_(_: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
Ok(args.expect::<Value>("value")?.type_name().into())
|
Ok(args.expect::<Value>("value")?.type_name().into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ensure that a condition is fulfilled.
|
/// Ensure that a condition is fulfilled.
|
||||||
pub fn assert(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn assert(_: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
let Spanned { v, span } = args.expect::<Spanned<bool>>("condition")?;
|
let Spanned { v, span } = args.expect::<Spanned<bool>>("condition")?;
|
||||||
if !v {
|
if !v {
|
||||||
bail!(span, "assertion failed");
|
bail!(span, "assertion failed");
|
||||||
@ -29,19 +29,18 @@ pub fn assert(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a string as Typst markup.
|
/// Evaluate a string as Typst markup.
|
||||||
pub fn eval(vm: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn eval(vm: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
let Spanned { v: src, span } = args.expect::<Spanned<String>>("source")?;
|
let Spanned { v: text, span } = args.expect::<Spanned<String>>("source")?;
|
||||||
|
|
||||||
// Parse the source and set a synthetic span for all nodes.
|
// Parse the source and set a synthetic span for all nodes.
|
||||||
let source = Source::synthesized(src, span);
|
let source = Source::synthesized(text, span);
|
||||||
let ast = source.ast()?;
|
let ast = source.ast()?;
|
||||||
|
|
||||||
// Evaluate the source.
|
// Evaluate the source.
|
||||||
let std = vm.ctx.config.std.clone();
|
let std = &vm.world.config().std;
|
||||||
let scopes = Scopes::new(Some(&std));
|
let scopes = Scopes::new(Some(std));
|
||||||
let mut sub = Machine::new(vm.ctx, vec![], scopes);
|
let mut sub = Vm::new(vm.world, vec![], scopes);
|
||||||
let result = ast.eval(&mut sub);
|
let result = ast.eval(&mut sub);
|
||||||
assert!(vm.deps.is_empty());
|
|
||||||
|
|
||||||
// Handle control flow.
|
// Handle control flow.
|
||||||
if let Some(flow) = sub.flow {
|
if let Some(flow) = sub.flow {
|
||||||
|
@ -2,12 +2,12 @@ use crate::eval::Regex;
|
|||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
|
|
||||||
/// The string representation of a value.
|
/// The string representation of a value.
|
||||||
pub fn repr(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn repr(_: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
Ok(args.expect::<Value>("value")?.repr().into())
|
Ok(args.expect::<Value>("value")?.repr().into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a value to a string.
|
/// Convert a value to a string.
|
||||||
pub fn str(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn str(_: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
let Spanned { v, span } = args.expect("value")?;
|
let Spanned { v, span } = args.expect("value")?;
|
||||||
Ok(Value::Str(match v {
|
Ok(Value::Str(match v {
|
||||||
Value::Int(v) => format_str!("{}", v),
|
Value::Int(v) => format_str!("{}", v),
|
||||||
@ -18,29 +18,29 @@ pub fn str(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create blind text.
|
/// Create blind text.
|
||||||
pub fn lorem(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn lorem(_: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
let words: usize = args.expect("number of words")?;
|
let words: usize = args.expect("number of words")?;
|
||||||
Ok(Value::Str(lipsum::lipsum(words).into()))
|
Ok(Value::Str(lipsum::lipsum(words).into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a regular expression.
|
/// Create a regular expression.
|
||||||
pub fn regex(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn regex(_: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
let Spanned { v, span } = args.expect::<Spanned<EcoString>>("regular expression")?;
|
let Spanned { v, span } = args.expect::<Spanned<EcoString>>("regular expression")?;
|
||||||
Ok(Regex::new(&v).at(span)?.into())
|
Ok(Regex::new(&v).at(span)?.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts an integer into one or multiple letters.
|
/// Converts an integer into one or multiple letters.
|
||||||
pub fn letter(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn letter(_: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
numbered(Numbering::Letter, args)
|
numbered(Numbering::Letter, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts an integer into a roman numeral.
|
/// Converts an integer into a roman numeral.
|
||||||
pub fn roman(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn roman(_: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
numbered(Numbering::Roman, args)
|
numbered(Numbering::Roman, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a number into a symbol.
|
/// Convert a number into a symbol.
|
||||||
pub fn symbol(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn symbol(_: &mut Vm, args: &mut Args) -> TypResult<Value> {
|
||||||
numbered(Numbering::Symbol, args)
|
numbered(Numbering::Symbol, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
239
src/loading.rs
239
src/loading.rs
@ -1,239 +0,0 @@
|
|||||||
//! Resource loading.
|
|
||||||
|
|
||||||
use std::fmt::{self, Debug, Formatter};
|
|
||||||
use std::io;
|
|
||||||
use std::ops::Deref;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use crate::font::{Font, FontBook};
|
|
||||||
use crate::util::Prehashed;
|
|
||||||
|
|
||||||
/// A hash that identifies a file.
|
|
||||||
///
|
|
||||||
/// Such a hash can be [resolved](Loader::resolve) from a path.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub struct FileHash(pub u64);
|
|
||||||
|
|
||||||
/// Loads resources from a local or remote source.
|
|
||||||
pub trait Loader {
|
|
||||||
/// Metadata about all known fonts.
|
|
||||||
fn book(&self) -> &FontBook;
|
|
||||||
|
|
||||||
/// Access the font with the given id.
|
|
||||||
fn font(&self, id: usize) -> io::Result<Font>;
|
|
||||||
|
|
||||||
/// Resolve a hash that is the same for this and all other paths pointing to
|
|
||||||
/// the same file.
|
|
||||||
fn resolve(&self, path: &Path) -> io::Result<FileHash>;
|
|
||||||
|
|
||||||
/// Load a file from a path.
|
|
||||||
fn file(&self, path: &Path) -> io::Result<Buffer>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A shared buffer that is cheap to clone.
|
|
||||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
|
||||||
pub struct Buffer(Prehashed<Arc<Vec<u8>>>);
|
|
||||||
|
|
||||||
impl Buffer {
|
|
||||||
/// Return a view into the buffer.
|
|
||||||
pub fn as_slice(&self) -> &[u8] {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a copy of the buffer as a vector.
|
|
||||||
pub fn to_vec(&self) -> Vec<u8> {
|
|
||||||
self.0.to_vec()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&[u8]> for Buffer {
|
|
||||||
fn from(slice: &[u8]) -> Self {
|
|
||||||
Self(Prehashed::new(Arc::new(slice.to_vec())))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Vec<u8>> for Buffer {
|
|
||||||
fn from(vec: Vec<u8>) -> Self {
|
|
||||||
Self(Prehashed::new(Arc::new(vec)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Arc<Vec<u8>>> for Buffer {
|
|
||||||
fn from(arc: Arc<Vec<u8>>) -> Self {
|
|
||||||
Self(Prehashed::new(arc))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for Buffer {
|
|
||||||
type Target = [u8];
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<[u8]> for Buffer {
|
|
||||||
fn as_ref(&self) -> &[u8] {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for Buffer {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
f.pad("Buffer(..)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "fs")]
|
|
||||||
pub use fs::*;
|
|
||||||
|
|
||||||
#[cfg(feature = "fs")]
|
|
||||||
mod fs {
|
|
||||||
use std::fs::{self, File};
|
|
||||||
use std::io;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
use memmap2::Mmap;
|
|
||||||
use same_file::Handle;
|
|
||||||
use walkdir::WalkDir;
|
|
||||||
|
|
||||||
use super::{Buffer, FileHash, Loader};
|
|
||||||
use crate::font::{Font, FontBook, FontInfo};
|
|
||||||
|
|
||||||
/// Loads fonts and files from the local file system.
|
|
||||||
///
|
|
||||||
/// _This is only available when the `system` feature is enabled._
|
|
||||||
pub struct FsLoader {
|
|
||||||
book: FontBook,
|
|
||||||
paths: Vec<(PathBuf, u32)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FsLoader {
|
|
||||||
/// Create a new system loader.
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self { book: FontBook::new(), paths: vec![] }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Builder-style variant of [`search_path`](Self::search_path).
|
|
||||||
pub fn with_path(mut self, dir: impl AsRef<Path>) -> Self {
|
|
||||||
self.search_path(dir);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Search for all fonts at a path.
|
|
||||||
///
|
|
||||||
/// If the path is a directory, all contained fonts will be searched for
|
|
||||||
/// recursively.
|
|
||||||
pub fn search_path(&mut self, path: impl AsRef<Path>) {
|
|
||||||
let walk = WalkDir::new(path)
|
|
||||||
.follow_links(true)
|
|
||||||
.sort_by(|a, b| a.file_name().cmp(b.file_name()))
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|e| e.ok());
|
|
||||||
|
|
||||||
for entry in walk {
|
|
||||||
let path = entry.path();
|
|
||||||
if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
|
|
||||||
if matches!(
|
|
||||||
ext,
|
|
||||||
"ttf" | "otf" | "TTF" | "OTF" | "ttc" | "otc" | "TTC" | "OTC",
|
|
||||||
) {
|
|
||||||
self.search_file(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Index the fonts in the file at the given path.
|
|
||||||
///
|
|
||||||
/// The file may form a font collection and contain multiple fonts,
|
|
||||||
/// which will then all be indexed.
|
|
||||||
fn search_file(&mut self, path: impl AsRef<Path>) {
|
|
||||||
let path = path.as_ref();
|
|
||||||
let path = path.strip_prefix(".").unwrap_or(path);
|
|
||||||
if let Ok(file) = File::open(path) {
|
|
||||||
if let Ok(mmap) = unsafe { Mmap::map(&file) } {
|
|
||||||
for (i, info) in FontInfo::from_data(&mmap).enumerate() {
|
|
||||||
self.book.push(info);
|
|
||||||
self.paths.push((path.into(), i as u32));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Builder-style variant of [`search_system`](Self::search_system).
|
|
||||||
pub fn with_system(mut self) -> Self {
|
|
||||||
self.search_system();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Search for fonts in the operating system's font directories.
|
|
||||||
pub fn search_system(&mut self) {
|
|
||||||
self.search_system_impl();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(all(unix, not(target_os = "macos")))]
|
|
||||||
fn search_system_impl(&mut self) {
|
|
||||||
self.search_path("/usr/share/fonts");
|
|
||||||
self.search_path("/usr/local/share/fonts");
|
|
||||||
|
|
||||||
if let Some(dir) = dirs::font_dir() {
|
|
||||||
self.search_path(dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
fn search_system_impl(&mut self) {
|
|
||||||
self.search_path("/Library/Fonts");
|
|
||||||
self.search_path("/Network/Library/Fonts");
|
|
||||||
self.search_path("/System/Library/Fonts");
|
|
||||||
|
|
||||||
if let Some(dir) = dirs::font_dir() {
|
|
||||||
self.search_path(dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
fn search_system_impl(&mut self) {
|
|
||||||
let windir =
|
|
||||||
std::env::var("WINDIR").unwrap_or_else(|_| "C:\\Windows".to_string());
|
|
||||||
|
|
||||||
self.search_path(Path::new(&windir).join("Fonts"));
|
|
||||||
|
|
||||||
if let Some(roaming) = dirs::config_dir() {
|
|
||||||
self.search_path(roaming.join("Microsoft\\Windows\\Fonts"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(local) = dirs::cache_dir() {
|
|
||||||
self.search_path(local.join("Microsoft\\Windows\\Fonts"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Loader for FsLoader {
|
|
||||||
fn book(&self) -> &FontBook {
|
|
||||||
&self.book
|
|
||||||
}
|
|
||||||
|
|
||||||
fn font(&self, id: usize) -> io::Result<Font> {
|
|
||||||
let (path, index) = &self.paths[id];
|
|
||||||
let data = self.file(path)?;
|
|
||||||
Font::new(data, *index).ok_or_else(|| io::ErrorKind::InvalidData.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve(&self, path: &Path) -> io::Result<FileHash> {
|
|
||||||
let meta = fs::metadata(path)?;
|
|
||||||
if meta.is_file() {
|
|
||||||
let handle = Handle::from_path(path)?;
|
|
||||||
Ok(FileHash(fxhash::hash64(&handle)))
|
|
||||||
} else {
|
|
||||||
Err(io::ErrorKind::NotFound.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn file(&self, path: &Path) -> io::Result<Buffer> {
|
|
||||||
Ok(fs::read(path)?.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
301
src/main.rs
301
src/main.rs
@ -1,22 +1,32 @@
|
|||||||
use std::fs;
|
use std::cell::RefCell;
|
||||||
|
use std::collections::{hash_map::Entry, HashMap};
|
||||||
|
use std::fs::{self, File};
|
||||||
|
use std::hash::Hash;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process;
|
use std::process;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
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 elsa::FrozenVec;
|
||||||
|
use memmap2::Mmap;
|
||||||
|
use once_cell::unsync::OnceCell;
|
||||||
use pico_args::Arguments;
|
use pico_args::Arguments;
|
||||||
use same_file::is_same_file;
|
use same_file::{is_same_file, Handle};
|
||||||
|
use siphasher::sip128::{Hasher128, SipHasher};
|
||||||
use termcolor::{ColorChoice, StandardStream, WriteColor};
|
use termcolor::{ColorChoice, StandardStream, WriteColor};
|
||||||
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use typst::diag::{Error, StrResult};
|
use typst::diag::{failed_to_load, Error, StrResult};
|
||||||
use typst::font::FontVariant;
|
use typst::font::{Font, FontBook, FontInfo, FontVariant};
|
||||||
use typst::library::text::THEME;
|
use typst::library::text::THEME;
|
||||||
use typst::loading::{FsLoader, Loader};
|
|
||||||
use typst::parse::TokenMode;
|
use typst::parse::TokenMode;
|
||||||
use typst::source::SourceStore;
|
use typst::source::{Source, SourceId};
|
||||||
use typst::{Config, Context};
|
use typst::util::Buffer;
|
||||||
|
use typst::{Config, World};
|
||||||
|
|
||||||
|
type CodespanResult<T> = Result<T, CodespanError>;
|
||||||
|
type CodespanError = codespan_reporting::files::Error;
|
||||||
|
|
||||||
/// What to do.
|
/// What to do.
|
||||||
enum Command {
|
enum Command {
|
||||||
@ -191,25 +201,20 @@ fn dispatch(command: Command) -> StrResult<()> {
|
|||||||
|
|
||||||
/// Execute a typesetting command.
|
/// Execute a typesetting command.
|
||||||
fn typeset(command: TypesetCommand) -> StrResult<()> {
|
fn typeset(command: TypesetCommand) -> StrResult<()> {
|
||||||
let mut config = Config::builder();
|
let mut world = SystemWorld::new();
|
||||||
if let Some(root) = &command.root {
|
if let Some(root) = &command.root {
|
||||||
config.root(root);
|
world.config.root = root.clone();
|
||||||
} else if let Some(dir) = command.input.parent() {
|
} else if let Some(dir) = command.input.parent() {
|
||||||
config.root(dir);
|
world.config.root = dir.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a loader for fonts and files.
|
// Create the world that serves sources, fonts and files.
|
||||||
let loader = FsLoader::new().with_system();
|
let id = world
|
||||||
|
.resolve(&command.input)
|
||||||
// Create the context which holds loaded source files, fonts, images and
|
.map_err(|err| failed_to_load("source file", &command.input, err))?;
|
||||||
// cached artifacts.
|
|
||||||
let mut ctx = Context::new(Arc::new(loader), config.build());
|
|
||||||
|
|
||||||
// Load the source file.
|
|
||||||
let id = ctx.sources.load(&command.input)?;
|
|
||||||
|
|
||||||
// Typeset.
|
// Typeset.
|
||||||
match typst::typeset(&mut ctx, id) {
|
match typst::typeset(&world, id) {
|
||||||
// Export the PDF.
|
// Export the PDF.
|
||||||
Ok(frames) => {
|
Ok(frames) => {
|
||||||
let buffer = typst::export::pdf(&frames);
|
let buffer = typst::export::pdf(&frames);
|
||||||
@ -218,7 +223,7 @@ fn typeset(command: TypesetCommand) -> StrResult<()> {
|
|||||||
|
|
||||||
// Print diagnostics.
|
// Print diagnostics.
|
||||||
Err(errors) => {
|
Err(errors) => {
|
||||||
print_diagnostics(&ctx.sources, *errors)
|
print_diagnostics(&world, *errors)
|
||||||
.map_err(|_| "failed to print diagnostics")?;
|
.map_err(|_| "failed to print diagnostics")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -228,7 +233,7 @@ fn typeset(command: TypesetCommand) -> StrResult<()> {
|
|||||||
|
|
||||||
/// Print diagnostic messages to the terminal.
|
/// Print diagnostic messages to the terminal.
|
||||||
fn print_diagnostics(
|
fn print_diagnostics(
|
||||||
sources: &SourceStore,
|
world: &SystemWorld,
|
||||||
errors: Vec<Error>,
|
errors: Vec<Error>,
|
||||||
) -> Result<(), codespan_reporting::files::Error> {
|
) -> Result<(), codespan_reporting::files::Error> {
|
||||||
let mut w = StandardStream::stderr(ColorChoice::Always);
|
let mut w = StandardStream::stderr(ColorChoice::Always);
|
||||||
@ -236,21 +241,24 @@ fn print_diagnostics(
|
|||||||
|
|
||||||
for error in errors {
|
for error in errors {
|
||||||
// The main diagnostic.
|
// The main diagnostic.
|
||||||
let range = sources.range(error.span);
|
let range = world.source(error.span.source()).range(error.span);
|
||||||
let diag = Diagnostic::error()
|
let diag = Diagnostic::error()
|
||||||
.with_message(error.message)
|
.with_message(error.message)
|
||||||
.with_labels(vec![Label::primary(error.span.source(), range)]);
|
.with_labels(vec![Label::primary(error.span.source(), range)]);
|
||||||
|
|
||||||
term::emit(&mut w, &config, sources, &diag)?;
|
term::emit(&mut w, &config, world, &diag)?;
|
||||||
|
|
||||||
// Stacktrace-like helper diagnostics.
|
// Stacktrace-like helper diagnostics.
|
||||||
for point in error.trace {
|
for point in error.trace {
|
||||||
let message = point.v.to_string();
|
let message = point.v.to_string();
|
||||||
let help = Diagnostic::help().with_message(message).with_labels(vec![
|
let help = Diagnostic::help().with_message(message).with_labels(vec![
|
||||||
Label::primary(point.span.source(), sources.range(point.span)),
|
Label::primary(
|
||||||
|
point.span.source(),
|
||||||
|
world.source(point.span.source()).range(point.span),
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
term::emit(&mut w, &config, sources, &help)?;
|
term::emit(&mut w, &config, world, &help)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,8 +267,8 @@ fn print_diagnostics(
|
|||||||
|
|
||||||
/// Execute a highlighting command.
|
/// Execute a highlighting command.
|
||||||
fn highlight(command: HighlightCommand) -> StrResult<()> {
|
fn highlight(command: HighlightCommand) -> StrResult<()> {
|
||||||
let input = std::fs::read_to_string(&command.input)
|
let input =
|
||||||
.map_err(|_| "failed to load source file")?;
|
fs::read_to_string(&command.input).map_err(|_| "failed to load source file")?;
|
||||||
|
|
||||||
let html = typst::syntax::highlight_html(&input, TokenMode::Markup, &THEME);
|
let html = typst::syntax::highlight_html(&input, TokenMode::Markup, &THEME);
|
||||||
fs::write(&command.output, html).map_err(|_| "failed to write HTML file")?;
|
fs::write(&command.output, html).map_err(|_| "failed to write HTML file")?;
|
||||||
@ -270,8 +278,8 @@ fn highlight(command: HighlightCommand) -> StrResult<()> {
|
|||||||
|
|
||||||
/// Execute a font listing command.
|
/// Execute a font listing command.
|
||||||
fn fonts(command: FontsCommand) -> StrResult<()> {
|
fn fonts(command: FontsCommand) -> StrResult<()> {
|
||||||
let loader = FsLoader::new().with_system();
|
let world = SystemWorld::new();
|
||||||
for (name, infos) in loader.book().families() {
|
for (name, infos) in world.book().families() {
|
||||||
println!("{name}");
|
println!("{name}");
|
||||||
if command.variants {
|
if command.variants {
|
||||||
for info in infos {
|
for info in infos {
|
||||||
@ -283,3 +291,234 @@ fn fonts(command: FontsCommand) -> StrResult<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A world that provides access to the operating system.
|
||||||
|
struct SystemWorld {
|
||||||
|
config: Config,
|
||||||
|
sources: FrozenVec<Box<Source>>,
|
||||||
|
nav: RefCell<HashMap<PathHash, SourceId>>,
|
||||||
|
book: FontBook,
|
||||||
|
fonts: Vec<FontSlot>,
|
||||||
|
files: RefCell<HashMap<PathHash, Buffer>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FontSlot {
|
||||||
|
path: PathBuf,
|
||||||
|
index: u32,
|
||||||
|
font: OnceCell<Option<Font>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SystemWorld {
|
||||||
|
fn new() -> Self {
|
||||||
|
let mut world = Self {
|
||||||
|
config: Config::default(),
|
||||||
|
book: FontBook::new(),
|
||||||
|
sources: FrozenVec::new(),
|
||||||
|
nav: RefCell::new(HashMap::new()),
|
||||||
|
fonts: vec![],
|
||||||
|
files: RefCell::new(HashMap::new()),
|
||||||
|
};
|
||||||
|
world.search_system();
|
||||||
|
world
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl World for SystemWorld {
|
||||||
|
fn config(&self) -> &Config {
|
||||||
|
&self.config
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve(&self, path: &Path) -> io::Result<SourceId> {
|
||||||
|
let hash = PathHash::new(path)?;
|
||||||
|
if let Some(&id) = self.nav.borrow().get(&hash) {
|
||||||
|
return Ok(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = fs::read(path)?;
|
||||||
|
let text = String::from_utf8(data).map_err(|_| {
|
||||||
|
io::Error::new(io::ErrorKind::InvalidData, "file is not valid utf-8")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let id = SourceId::from_raw(self.sources.len() as u16);
|
||||||
|
let source = Source::new(id, path, text);
|
||||||
|
self.sources.push(Box::new(source));
|
||||||
|
self.nav.borrow_mut().insert(hash, id);
|
||||||
|
|
||||||
|
Ok(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn source(&self, id: SourceId) -> &Source {
|
||||||
|
&self.sources[id.into_raw() as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn book(&self) -> &FontBook {
|
||||||
|
&self.book
|
||||||
|
}
|
||||||
|
|
||||||
|
fn font(&self, id: usize) -> io::Result<Font> {
|
||||||
|
let slot = &self.fonts[id];
|
||||||
|
slot.font
|
||||||
|
.get_or_init(|| {
|
||||||
|
let data = self.file(&slot.path).ok()?;
|
||||||
|
Font::new(data, slot.index)
|
||||||
|
})
|
||||||
|
.clone()
|
||||||
|
.ok_or_else(|| io::ErrorKind::InvalidData.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn file(&self, path: &Path) -> io::Result<Buffer> {
|
||||||
|
let hash = PathHash::new(path)?;
|
||||||
|
Ok(match self.files.borrow_mut().entry(hash) {
|
||||||
|
Entry::Occupied(entry) => entry.get().clone(),
|
||||||
|
Entry::Vacant(entry) => entry.insert(fs::read(path)?.into()).clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A hash that is the same for all paths pointing to the same file.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
struct PathHash(u128);
|
||||||
|
|
||||||
|
impl PathHash {
|
||||||
|
fn new(path: &Path) -> io::Result<Self> {
|
||||||
|
let file = File::open(path)?;
|
||||||
|
if file.metadata()?.is_file() {
|
||||||
|
let handle = Handle::from_file(file)?;
|
||||||
|
let mut state = SipHasher::new();
|
||||||
|
handle.hash(&mut state);
|
||||||
|
Ok(Self(state.finish128().as_u128()))
|
||||||
|
} else {
|
||||||
|
Err(io::ErrorKind::NotFound.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SystemWorld {
|
||||||
|
/// Search for fonts in the linux system font directories.
|
||||||
|
#[cfg(all(unix, not(target_os = "macos")))]
|
||||||
|
fn search_system(&mut self) {
|
||||||
|
self.search_dir("/usr/share/fonts");
|
||||||
|
self.search_dir("/usr/local/share/fonts");
|
||||||
|
|
||||||
|
if let Some(dir) = dirs::font_dir() {
|
||||||
|
self.search_dir(dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Search for fonts in the macOS system font directories.
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn search_system(&mut self) {
|
||||||
|
self.search_dir("/Library/Fonts");
|
||||||
|
self.search_dir("/Network/Library/Fonts");
|
||||||
|
self.search_dir("/System/Library/Fonts");
|
||||||
|
|
||||||
|
if let Some(dir) = dirs::font_dir() {
|
||||||
|
self.search_dir(dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Search for fonts in the Windows system font directories.
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn search_system(&mut self) {
|
||||||
|
let windir =
|
||||||
|
std::env::var("WINDIR").unwrap_or_else(|_| "C:\\Windows".to_string());
|
||||||
|
|
||||||
|
self.search_dir(Path::new(&windir).join("Fonts"));
|
||||||
|
|
||||||
|
if let Some(roaming) = dirs::config_dir() {
|
||||||
|
self.search_dir(roaming.join("Microsoft\\Windows\\Fonts"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(local) = dirs::cache_dir() {
|
||||||
|
self.search_dir(local.join("Microsoft\\Windows\\Fonts"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Search for all fonts in a directory.
|
||||||
|
/// recursively.
|
||||||
|
fn search_dir(&mut self, path: impl AsRef<Path>) {
|
||||||
|
for entry in WalkDir::new(path)
|
||||||
|
.follow_links(true)
|
||||||
|
.sort_by(|a, b| a.file_name().cmp(b.file_name()))
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
{
|
||||||
|
let path = entry.path();
|
||||||
|
if matches!(
|
||||||
|
path.extension().and_then(|s| s.to_str()),
|
||||||
|
Some("ttf" | "otf" | "TTF" | "OTF" | "ttc" | "otc" | "TTC" | "OTC"),
|
||||||
|
) {
|
||||||
|
self.search_file(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Index the fonts in the file at the given path.
|
||||||
|
fn search_file(&mut self, path: impl AsRef<Path>) {
|
||||||
|
let path = path.as_ref();
|
||||||
|
if let Ok(file) = File::open(path) {
|
||||||
|
if let Ok(mmap) = unsafe { Mmap::map(&file) } {
|
||||||
|
for (i, info) in FontInfo::from_data(&mmap).enumerate() {
|
||||||
|
self.book.push(info);
|
||||||
|
self.fonts.push(FontSlot {
|
||||||
|
path: path.into(),
|
||||||
|
index: i as u32,
|
||||||
|
font: OnceCell::new(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> codespan_reporting::files::Files<'a> for SystemWorld {
|
||||||
|
type FileId = SourceId;
|
||||||
|
type Name = std::path::Display<'a>;
|
||||||
|
type Source = &'a str;
|
||||||
|
|
||||||
|
fn name(&'a self, id: SourceId) -> CodespanResult<Self::Name> {
|
||||||
|
Ok(World::source(self, id).path().display())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn source(&'a self, id: SourceId) -> CodespanResult<Self::Source> {
|
||||||
|
Ok(World::source(self, id).text())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line_index(&'a self, id: SourceId, given: usize) -> CodespanResult<usize> {
|
||||||
|
let source = World::source(self, id);
|
||||||
|
source
|
||||||
|
.byte_to_line(given)
|
||||||
|
.ok_or_else(|| CodespanError::IndexTooLarge {
|
||||||
|
given,
|
||||||
|
max: source.len_bytes(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line_range(
|
||||||
|
&'a self,
|
||||||
|
id: SourceId,
|
||||||
|
given: usize,
|
||||||
|
) -> CodespanResult<std::ops::Range<usize>> {
|
||||||
|
let source = World::source(self, id);
|
||||||
|
source
|
||||||
|
.line_to_range(given)
|
||||||
|
.ok_or_else(|| CodespanError::LineTooLarge { given, max: source.len_lines() })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn column_number(
|
||||||
|
&'a self,
|
||||||
|
id: SourceId,
|
||||||
|
_: usize,
|
||||||
|
given: usize,
|
||||||
|
) -> CodespanResult<usize> {
|
||||||
|
let source = World::source(self, id);
|
||||||
|
source.byte_to_column(given).ok_or_else(|| {
|
||||||
|
let max = source.len_bytes();
|
||||||
|
if given <= max {
|
||||||
|
CodespanError::InvalidCharBoundary { given }
|
||||||
|
} else {
|
||||||
|
CodespanError::IndexTooLarge { given, max }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -18,20 +18,20 @@ use crate::library::text::{
|
|||||||
DecoNode, EmphNode, ParChild, ParNode, StrongNode, UNDERLINE,
|
DecoNode, EmphNode, ParChild, ParNode, StrongNode, UNDERLINE,
|
||||||
};
|
};
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
use crate::World;
|
||||||
|
|
||||||
/// Layout content into a collection of pages.
|
/// Layout content into a collection of pages.
|
||||||
///
|
///
|
||||||
/// Relayouts until all pinned locations are converged.
|
/// Relayouts until all pinned locations are converged.
|
||||||
pub fn layout(ctx: &mut Context, content: &Content) -> TypResult<Vec<Frame>> {
|
pub fn layout(world: &dyn World, content: &Content) -> TypResult<Vec<Frame>> {
|
||||||
let copy = ctx.config.styles.clone();
|
let styles = StyleChain::with_root(&world.config().styles);
|
||||||
let styles = StyleChain::with_root(©);
|
|
||||||
let scratch = Scratch::default();
|
let scratch = Scratch::default();
|
||||||
|
|
||||||
let mut builder = Builder::new(ctx, &scratch, true);
|
let mut builder = Builder::new(world, &scratch, true);
|
||||||
builder.accept(content, styles)?;
|
builder.accept(content, styles)?;
|
||||||
|
|
||||||
let (doc, shared) = builder.into_doc(styles)?;
|
let (doc, shared) = builder.into_doc(styles)?;
|
||||||
doc.layout(ctx, shared)
|
doc.layout(world, shared)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Composable representation of styled content.
|
/// Composable representation of styled content.
|
||||||
@ -232,15 +232,15 @@ impl Content {
|
|||||||
impl Layout for Content {
|
impl Layout for Content {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
let scratch = Scratch::default();
|
let scratch = Scratch::default();
|
||||||
let mut builder = Builder::new(ctx, &scratch, false);
|
let mut builder = Builder::new(world, &scratch, false);
|
||||||
builder.accept(self, styles)?;
|
builder.accept(self, styles)?;
|
||||||
let (flow, shared) = builder.into_flow(styles)?;
|
let (flow, shared) = builder.into_flow(styles)?;
|
||||||
flow.layout(ctx, regions, shared)
|
flow.layout(world, regions, shared)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pack(self) -> LayoutNode {
|
fn pack(self) -> LayoutNode {
|
||||||
@ -330,9 +330,9 @@ impl Sum for Content {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a document or a flow node from content.
|
/// Builds a document or a flow node from content.
|
||||||
struct Builder<'a, 'ctx> {
|
struct Builder<'a, 'w> {
|
||||||
/// The core context.
|
/// The core context.
|
||||||
ctx: &'ctx mut Context,
|
world: &'w dyn World,
|
||||||
/// Scratch arenas for building.
|
/// Scratch arenas for building.
|
||||||
scratch: &'a Scratch<'a>,
|
scratch: &'a Scratch<'a>,
|
||||||
/// The current document building state.
|
/// The current document building state.
|
||||||
@ -354,10 +354,10 @@ struct Scratch<'a> {
|
|||||||
templates: Arena<Content>,
|
templates: Arena<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'ctx> Builder<'a, 'ctx> {
|
impl<'a, 'w> Builder<'a, 'w> {
|
||||||
fn new(ctx: &'ctx mut Context, scratch: &'a Scratch<'a>, top: bool) -> Self {
|
fn new(world: &'w dyn World, scratch: &'a Scratch<'a>, top: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
ctx,
|
world,
|
||||||
scratch,
|
scratch,
|
||||||
doc: top.then(|| DocBuilder::default()),
|
doc: top.then(|| DocBuilder::default()),
|
||||||
flow: FlowBuilder::default(),
|
flow: FlowBuilder::default(),
|
||||||
@ -388,7 +388,7 @@ impl<'a, 'ctx> Builder<'a, 'ctx> {
|
|||||||
match content {
|
match content {
|
||||||
Content::Empty => return Ok(()),
|
Content::Empty => return Ok(()),
|
||||||
Content::Text(text) => {
|
Content::Text(text) => {
|
||||||
if let Some(realized) = styles.apply(self.ctx, Target::Text(text))? {
|
if let Some(realized) = styles.apply(self.world, Target::Text(text))? {
|
||||||
let stored = self.scratch.templates.alloc(realized);
|
let stored = self.scratch.templates.alloc(realized);
|
||||||
return self.accept(stored, styles);
|
return self.accept(stored, styles);
|
||||||
}
|
}
|
||||||
@ -431,7 +431,7 @@ impl<'a, 'ctx> Builder<'a, 'ctx> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn show(&mut self, node: &ShowNode, styles: StyleChain<'a>) -> TypResult<()> {
|
fn show(&mut self, node: &ShowNode, styles: StyleChain<'a>) -> TypResult<()> {
|
||||||
if let Some(mut realized) = styles.apply(self.ctx, Target::Node(node))? {
|
if let Some(mut realized) = styles.apply(self.world, Target::Node(node))? {
|
||||||
let mut map = StyleMap::new();
|
let mut map = StyleMap::new();
|
||||||
let barrier = Barrier::new(node.id());
|
let barrier = Barrier::new(node.id());
|
||||||
map.push(StyleEntry::Barrier(barrier));
|
map.push(StyleEntry::Barrier(barrier));
|
||||||
|
@ -15,7 +15,7 @@ use crate::geom::{
|
|||||||
use crate::library::graphics::MoveNode;
|
use crate::library::graphics::MoveNode;
|
||||||
use crate::library::layout::{AlignNode, PadNode};
|
use crate::library::layout::{AlignNode, PadNode};
|
||||||
use crate::util::Prehashed;
|
use crate::util::Prehashed;
|
||||||
use crate::Context;
|
use crate::World;
|
||||||
|
|
||||||
/// A node that can be layouted into a sequence of regions.
|
/// A node that can be layouted into a sequence of regions.
|
||||||
///
|
///
|
||||||
@ -24,7 +24,7 @@ pub trait Layout: 'static {
|
|||||||
/// Layout this node into the given regions, producing frames.
|
/// Layout this node into the given regions, producing frames.
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Frame>>;
|
) -> TypResult<Vec<Frame>>;
|
||||||
@ -216,14 +216,14 @@ impl LayoutNode {
|
|||||||
impl Layout for LayoutNode {
|
impl Layout for LayoutNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
let barrier = StyleEntry::Barrier(Barrier::new(self.id()));
|
let barrier = StyleEntry::Barrier(Barrier::new(self.id()));
|
||||||
let styles = barrier.chain(&styles);
|
let styles = barrier.chain(&styles);
|
||||||
|
|
||||||
let mut frames = self.0.layout(ctx, regions, styles)?;
|
let mut frames = self.0.layout(world, regions, styles)?;
|
||||||
if let Some(role) = styles.role() {
|
if let Some(role) = styles.role() {
|
||||||
for frame in &mut frames {
|
for frame in &mut frames {
|
||||||
frame.apply_role(role);
|
frame.apply_role(role);
|
||||||
@ -285,7 +285,7 @@ struct EmptyNode;
|
|||||||
impl Layout for EmptyNode {
|
impl Layout for EmptyNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
_: &mut Context,
|
_: &dyn World,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
_: StyleChain,
|
_: StyleChain,
|
||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
@ -307,7 +307,7 @@ struct SizedNode {
|
|||||||
impl Layout for SizedNode {
|
impl Layout for SizedNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
@ -331,7 +331,7 @@ impl Layout for SizedNode {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Layout the child.
|
// Layout the child.
|
||||||
let mut frames = self.child.layout(ctx, &pod, styles)?;
|
let mut frames = self.child.layout(world, &pod, styles)?;
|
||||||
|
|
||||||
// Ensure frame size matches regions size if expansion is on.
|
// Ensure frame size matches regions size if expansion is on.
|
||||||
let frame = &mut frames[0];
|
let frame = &mut frames[0];
|
||||||
@ -354,11 +354,11 @@ struct FillNode {
|
|||||||
impl Layout for FillNode {
|
impl Layout for FillNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
let mut frames = self.child.layout(ctx, regions, styles)?;
|
let mut frames = self.child.layout(world, regions, styles)?;
|
||||||
for frame in &mut frames {
|
for frame in &mut frames {
|
||||||
let shape = Geometry::Rect(frame.size()).filled(self.fill);
|
let shape = Geometry::Rect(frame.size()).filled(self.fill);
|
||||||
frame.prepend(Point::zero(), Element::Shape(shape));
|
frame.prepend(Point::zero(), Element::Shape(shape));
|
||||||
@ -379,11 +379,11 @@ struct StrokeNode {
|
|||||||
impl Layout for StrokeNode {
|
impl Layout for StrokeNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
let mut frames = self.child.layout(ctx, regions, styles)?;
|
let mut frames = self.child.layout(world, regions, styles)?;
|
||||||
for frame in &mut frames {
|
for frame in &mut frames {
|
||||||
let shape = Geometry::Rect(frame.size()).stroked(self.stroke);
|
let shape = Geometry::Rect(frame.size()).stroked(self.stroke);
|
||||||
frame.prepend(Point::zero(), Element::Shape(shape));
|
frame.prepend(Point::zero(), Element::Shape(shape));
|
||||||
|
@ -5,7 +5,7 @@ use crate::diag::TypResult;
|
|||||||
use crate::eval::{Args, Func, Regex, Value};
|
use crate::eval::{Args, Func, Regex, Value};
|
||||||
use crate::library::structure::{EnumNode, ListNode};
|
use crate::library::structure::{EnumNode, ListNode};
|
||||||
use crate::syntax::Spanned;
|
use crate::syntax::Spanned;
|
||||||
use crate::Context;
|
use crate::World;
|
||||||
|
|
||||||
/// A show rule recipe.
|
/// A show rule recipe.
|
||||||
#[derive(Clone, PartialEq, Hash)]
|
#[derive(Clone, PartialEq, Hash)]
|
||||||
@ -29,7 +29,7 @@ impl Recipe {
|
|||||||
/// Try to apply the recipe to the target.
|
/// Try to apply the recipe to the target.
|
||||||
pub fn apply(
|
pub fn apply(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
sel: Selector,
|
sel: Selector,
|
||||||
target: Target,
|
target: Target,
|
||||||
@ -37,7 +37,7 @@ impl Recipe {
|
|||||||
let content = match (target, &self.pattern) {
|
let content = match (target, &self.pattern) {
|
||||||
(Target::Node(node), &Pattern::Node(id)) if node.id() == id => {
|
(Target::Node(node), &Pattern::Node(id)) if node.id() == id => {
|
||||||
let node = node.unguard(sel);
|
let node = node.unguard(sel);
|
||||||
self.call(ctx, || {
|
self.call(world, || {
|
||||||
let dict = node.encode(styles);
|
let dict = node.encode(styles);
|
||||||
Value::Content(Content::Show(node, Some(dict)))
|
Value::Content(Content::Show(node, Some(dict)))
|
||||||
})?
|
})?
|
||||||
@ -53,7 +53,7 @@ impl Recipe {
|
|||||||
result.push(Content::Text(text[cursor .. start].into()));
|
result.push(Content::Text(text[cursor .. start].into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push(self.call(ctx, || Value::Str(mat.as_str().into()))?);
|
result.push(self.call(world, || Value::Str(mat.as_str().into()))?);
|
||||||
cursor = mat.end();
|
cursor = mat.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ impl Recipe {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Call the recipe function, with the argument if desired.
|
/// Call the recipe function, with the argument if desired.
|
||||||
fn call<F>(&self, ctx: &mut Context, arg: F) -> TypResult<Content>
|
fn call<F>(&self, world: &dyn World, arg: F) -> TypResult<Content>
|
||||||
where
|
where
|
||||||
F: FnOnce() -> Value,
|
F: FnOnce() -> Value,
|
||||||
{
|
{
|
||||||
@ -85,7 +85,7 @@ impl Recipe {
|
|||||||
Args::new(self.func.span, [arg()])
|
Args::new(self.func.span, [arg()])
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(self.func.v.call_detached(ctx, args)?.display())
|
Ok(self.func.v.call_detached(world, args)?.display())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What kind of structure the property interrupts.
|
/// What kind of structure the property interrupts.
|
||||||
|
@ -6,7 +6,7 @@ use super::{Content, NodeId, Selector, StyleChain};
|
|||||||
use crate::diag::TypResult;
|
use crate::diag::TypResult;
|
||||||
use crate::eval::Dict;
|
use crate::eval::Dict;
|
||||||
use crate::util::Prehashed;
|
use crate::util::Prehashed;
|
||||||
use crate::Context;
|
use crate::World;
|
||||||
|
|
||||||
/// A node that can be realized given some styles.
|
/// A node that can be realized given some styles.
|
||||||
pub trait Show: 'static {
|
pub trait Show: 'static {
|
||||||
@ -18,7 +18,7 @@ pub trait Show: 'static {
|
|||||||
|
|
||||||
/// The base recipe for this node that is executed if there is no
|
/// The base recipe for this node that is executed if there is no
|
||||||
/// user-defined show rule.
|
/// user-defined show rule.
|
||||||
fn realize(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content>;
|
fn realize(&self, world: &dyn World, styles: StyleChain) -> TypResult<Content>;
|
||||||
|
|
||||||
/// Finalize this node given the realization of a base or user recipe. Use
|
/// Finalize this node given the realization of a base or user recipe. Use
|
||||||
/// this for effects that should work even in the face of a user-defined
|
/// this for effects that should work even in the face of a user-defined
|
||||||
@ -30,7 +30,7 @@ pub trait Show: 'static {
|
|||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn finalize(
|
fn finalize(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
realized: Content,
|
realized: Content,
|
||||||
) -> TypResult<Content> {
|
) -> TypResult<Content> {
|
||||||
@ -74,17 +74,17 @@ impl Show for ShowNode {
|
|||||||
self.0.encode(styles)
|
self.0.encode(styles)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
fn realize(&self, world: &dyn World, styles: StyleChain) -> TypResult<Content> {
|
||||||
self.0.realize(ctx, styles)
|
self.0.realize(world, styles)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finalize(
|
fn finalize(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut Context,
|
world: &dyn World,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
realized: Content,
|
realized: Content,
|
||||||
) -> TypResult<Content> {
|
) -> TypResult<Content> {
|
||||||
self.0.finalize(ctx, styles, realized)
|
self.0.finalize(world, styles, realized)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pack(self) -> ShowNode {
|
fn pack(self) -> ShowNode {
|
||||||
|
@ -8,7 +8,7 @@ use crate::diag::TypResult;
|
|||||||
use crate::frame::Role;
|
use crate::frame::Role;
|
||||||
use crate::library::text::{FontFamily, TextNode};
|
use crate::library::text::{FontFamily, TextNode};
|
||||||
use crate::util::ReadableTypeId;
|
use crate::util::ReadableTypeId;
|
||||||
use crate::Context;
|
use crate::World;
|
||||||
|
|
||||||
/// A map of style properties.
|
/// A map of style properties.
|
||||||
#[derive(Default, Clone, PartialEq, Hash)]
|
#[derive(Default, Clone, PartialEq, Hash)]
|
||||||
@ -277,7 +277,7 @@ impl<'a> StyleChain<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Apply show recipes in this style chain to a target.
|
/// Apply show recipes in this style chain to a target.
|
||||||
pub fn apply(self, ctx: &mut Context, target: Target) -> TypResult<Option<Content>> {
|
pub fn apply(self, world: &dyn World, target: Target) -> TypResult<Option<Content>> {
|
||||||
// Find out how many recipes there any and whether any of their patterns
|
// Find out how many recipes there any and whether any of their patterns
|
||||||
// match.
|
// match.
|
||||||
let mut n = 0;
|
let mut n = 0;
|
||||||
@ -296,7 +296,9 @@ impl<'a> StyleChain<'a> {
|
|||||||
let sel = Selector::Nth(n);
|
let sel = Selector::Nth(n);
|
||||||
if self.guarded(sel) {
|
if self.guarded(sel) {
|
||||||
guarded = true;
|
guarded = true;
|
||||||
} else if let Some(content) = recipe.apply(ctx, self, sel, target)? {
|
} else if let Some(content) =
|
||||||
|
recipe.apply(world, self, sel, target)?
|
||||||
|
{
|
||||||
realized = Some(content);
|
realized = Some(content);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -312,7 +314,7 @@ impl<'a> StyleChain<'a> {
|
|||||||
if self.guarded(sel) {
|
if self.guarded(sel) {
|
||||||
guarded = true;
|
guarded = true;
|
||||||
} else {
|
} else {
|
||||||
let content = node.unguard(sel).realize(ctx, self)?;
|
let content = node.unguard(sel).realize(world, self)?;
|
||||||
realized = Some(content.styled_with_entry(StyleEntry::Guard(sel)));
|
realized = Some(content.styled_with_entry(StyleEntry::Guard(sel)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -320,7 +322,7 @@ impl<'a> StyleChain<'a> {
|
|||||||
// Finalize only if guarding didn't stop any recipe.
|
// Finalize only if guarding didn't stop any recipe.
|
||||||
if !guarded {
|
if !guarded {
|
||||||
if let Some(content) = realized {
|
if let Some(content) = realized {
|
||||||
realized = Some(node.finalize(ctx, self, content)?);
|
realized = Some(node.finalize(world, self, content)?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,21 +15,21 @@ use super::{
|
|||||||
/// Returns the range in the new source that was ultimately reparsed.
|
/// Returns the range in the new source that was ultimately reparsed.
|
||||||
pub fn reparse(
|
pub fn reparse(
|
||||||
root: &mut SyntaxNode,
|
root: &mut SyntaxNode,
|
||||||
src: &str,
|
text: &str,
|
||||||
replaced: Range<usize>,
|
replaced: Range<usize>,
|
||||||
replacement_len: usize,
|
replacement_len: usize,
|
||||||
) -> Range<usize> {
|
) -> Range<usize> {
|
||||||
if let SyntaxNode::Inner(inner) = root {
|
if let SyntaxNode::Inner(inner) = root {
|
||||||
let change = Change { src, replaced, replacement_len };
|
let change = Change { text, replaced, replacement_len };
|
||||||
if let Some(range) = try_reparse(&change, Arc::make_mut(inner), 0, true, true) {
|
if let Some(range) = try_reparse(&change, Arc::make_mut(inner), 0, true, true) {
|
||||||
return range;
|
return range;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let id = root.span().source();
|
let id = root.span().source();
|
||||||
*root = parse(src);
|
*root = parse(text);
|
||||||
root.numberize(id, Span::FULL).unwrap();
|
root.numberize(id, Span::FULL).unwrap();
|
||||||
0 .. src.len()
|
0 .. text.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to reparse inside the given node.
|
/// Try to reparse inside the given node.
|
||||||
@ -228,27 +228,27 @@ fn replace(
|
|||||||
let newborn_span = superseded_span.start .. newborn_end;
|
let newborn_span = superseded_span.start .. newborn_end;
|
||||||
|
|
||||||
let mut prefix = "";
|
let mut prefix = "";
|
||||||
for (i, c) in change.src[.. newborn_span.start].char_indices().rev() {
|
for (i, c) in change.text[.. newborn_span.start].char_indices().rev() {
|
||||||
if is_newline(c) {
|
if is_newline(c) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
prefix = &change.src[i .. newborn_span.start];
|
prefix = &change.text[i .. newborn_span.start];
|
||||||
}
|
}
|
||||||
|
|
||||||
let (newborns, terminated, amount) = match mode {
|
let (newborns, terminated, amount) = match mode {
|
||||||
ReparseMode::Code => reparse_code_block(
|
ReparseMode::Code => reparse_code_block(
|
||||||
&prefix,
|
&prefix,
|
||||||
&change.src[newborn_span.start ..],
|
&change.text[newborn_span.start ..],
|
||||||
newborn_span.len(),
|
newborn_span.len(),
|
||||||
),
|
),
|
||||||
ReparseMode::Content => reparse_content_block(
|
ReparseMode::Content => reparse_content_block(
|
||||||
&prefix,
|
&prefix,
|
||||||
&change.src[newborn_span.start ..],
|
&change.text[newborn_span.start ..],
|
||||||
newborn_span.len(),
|
newborn_span.len(),
|
||||||
),
|
),
|
||||||
ReparseMode::MarkupElements { at_start, min_indent } => reparse_markup_elements(
|
ReparseMode::MarkupElements { at_start, min_indent } => reparse_markup_elements(
|
||||||
&prefix,
|
&prefix,
|
||||||
&change.src[newborn_span.start ..],
|
&change.text[newborn_span.start ..],
|
||||||
newborn_span.len(),
|
newborn_span.len(),
|
||||||
differential,
|
differential,
|
||||||
&node.children().as_slice()[superseded_start ..],
|
&node.children().as_slice()[superseded_start ..],
|
||||||
@ -272,7 +272,7 @@ fn replace(
|
|||||||
/// A description of a change.
|
/// A description of a change.
|
||||||
struct Change<'a> {
|
struct Change<'a> {
|
||||||
/// The new source code, with the change applied.
|
/// The new source code, with the change applied.
|
||||||
src: &'a str,
|
text: &'a str,
|
||||||
/// Which range in the old source file was changed.
|
/// Which range in the old source file was changed.
|
||||||
replaced: Range<usize>,
|
replaced: Range<usize>,
|
||||||
/// How many characters replaced the text in `replaced`.
|
/// How many characters replaced the text in `replaced`.
|
||||||
@ -396,7 +396,7 @@ mod tests {
|
|||||||
fn test(prev: &str, range: Range<usize>, with: &str, goal: Range<usize>) {
|
fn test(prev: &str, range: Range<usize>, with: &str, goal: Range<usize>) {
|
||||||
let mut source = Source::detached(prev);
|
let mut source = Source::detached(prev);
|
||||||
let range = source.edit(range, with);
|
let range = source.edit(range, with);
|
||||||
check(source.src(), source.root(), &parse(source.src()));
|
check(source.text(), source.root(), &parse(source.text()));
|
||||||
assert_eq!(range, goal);
|
assert_eq!(range, goal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,15 +16,15 @@ use crate::syntax::{NodeKind, SpanPos, SyntaxNode};
|
|||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
/// Parse a source file.
|
/// Parse a source file.
|
||||||
pub fn parse(src: &str) -> SyntaxNode {
|
pub fn parse(text: &str) -> SyntaxNode {
|
||||||
let mut p = Parser::new(src, TokenMode::Markup);
|
let mut p = Parser::new(text, TokenMode::Markup);
|
||||||
markup(&mut p, true);
|
markup(&mut p, true);
|
||||||
p.finish().into_iter().next().unwrap()
|
p.finish().into_iter().next().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse code directly, only used for syntax highlighting.
|
/// Parse code directly, only used for syntax highlighting.
|
||||||
pub fn parse_code(src: &str) -> Vec<SyntaxNode> {
|
pub fn parse_code(text: &str) -> Vec<SyntaxNode> {
|
||||||
let mut p = Parser::new(src, TokenMode::Code);
|
let mut p = Parser::new(text, TokenMode::Code);
|
||||||
code(&mut p);
|
code(&mut p);
|
||||||
p.finish()
|
p.finish()
|
||||||
}
|
}
|
||||||
@ -34,10 +34,10 @@ pub fn parse_code(src: &str) -> Vec<SyntaxNode> {
|
|||||||
/// Returns `Some` if all of the input was consumed.
|
/// Returns `Some` if all of the input was consumed.
|
||||||
fn reparse_code_block(
|
fn reparse_code_block(
|
||||||
prefix: &str,
|
prefix: &str,
|
||||||
src: &str,
|
text: &str,
|
||||||
end_pos: usize,
|
end_pos: usize,
|
||||||
) -> Option<(Vec<SyntaxNode>, bool, usize)> {
|
) -> Option<(Vec<SyntaxNode>, bool, usize)> {
|
||||||
let mut p = Parser::with_prefix(prefix, src, TokenMode::Code);
|
let mut p = Parser::with_prefix(prefix, text, TokenMode::Code);
|
||||||
if !p.at(NodeKind::LeftBrace) {
|
if !p.at(NodeKind::LeftBrace) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@ -58,10 +58,10 @@ fn reparse_code_block(
|
|||||||
/// Returns `Some` if all of the input was consumed.
|
/// Returns `Some` if all of the input was consumed.
|
||||||
fn reparse_content_block(
|
fn reparse_content_block(
|
||||||
prefix: &str,
|
prefix: &str,
|
||||||
src: &str,
|
text: &str,
|
||||||
end_pos: usize,
|
end_pos: usize,
|
||||||
) -> Option<(Vec<SyntaxNode>, bool, usize)> {
|
) -> Option<(Vec<SyntaxNode>, bool, usize)> {
|
||||||
let mut p = Parser::with_prefix(prefix, src, TokenMode::Code);
|
let mut p = Parser::with_prefix(prefix, text, TokenMode::Code);
|
||||||
if !p.at(NodeKind::LeftBracket) {
|
if !p.at(NodeKind::LeftBracket) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@ -82,14 +82,14 @@ fn reparse_content_block(
|
|||||||
/// Returns `Some` if all of the input was consumed.
|
/// Returns `Some` if all of the input was consumed.
|
||||||
fn reparse_markup_elements(
|
fn reparse_markup_elements(
|
||||||
prefix: &str,
|
prefix: &str,
|
||||||
src: &str,
|
text: &str,
|
||||||
end_pos: usize,
|
end_pos: usize,
|
||||||
differential: isize,
|
differential: isize,
|
||||||
reference: &[SyntaxNode],
|
reference: &[SyntaxNode],
|
||||||
mut at_start: bool,
|
mut at_start: bool,
|
||||||
min_indent: usize,
|
min_indent: usize,
|
||||||
) -> Option<(Vec<SyntaxNode>, bool, usize)> {
|
) -> Option<(Vec<SyntaxNode>, bool, usize)> {
|
||||||
let mut p = Parser::with_prefix(prefix, src, TokenMode::Markup);
|
let mut p = Parser::with_prefix(prefix, text, TokenMode::Markup);
|
||||||
|
|
||||||
let mut node: Option<&SyntaxNode> = None;
|
let mut node: Option<&SyntaxNode> = None;
|
||||||
let mut iter = reference.iter();
|
let mut iter = reference.iter();
|
||||||
@ -996,12 +996,12 @@ mod tests {
|
|||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn check<T>(src: &str, found: T, expected: T)
|
pub fn check<T>(text: &str, found: T, expected: T)
|
||||||
where
|
where
|
||||||
T: Debug + PartialEq,
|
T: Debug + PartialEq,
|
||||||
{
|
{
|
||||||
if found != expected {
|
if found != expected {
|
||||||
println!("source: {src:?}");
|
println!("source: {text:?}");
|
||||||
println!("expected: {expected:#?}");
|
println!("expected: {expected:#?}");
|
||||||
println!("found: {found:#?}");
|
println!("found: {found:#?}");
|
||||||
panic!("test failed");
|
panic!("test failed");
|
||||||
|
@ -30,15 +30,15 @@ pub struct Parser<'s> {
|
|||||||
|
|
||||||
impl<'s> Parser<'s> {
|
impl<'s> Parser<'s> {
|
||||||
/// Create a new parser for the source string.
|
/// Create a new parser for the source string.
|
||||||
pub fn new(src: &'s str, mode: TokenMode) -> Self {
|
pub fn new(text: &'s str, mode: TokenMode) -> Self {
|
||||||
Self::with_prefix("", src, mode)
|
Self::with_prefix("", text, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new parser for the source string that is prefixed by some text
|
/// Create a new parser for the source string that is prefixed by some text
|
||||||
/// that does not need to be parsed but taken into account for column
|
/// that does not need to be parsed but taken into account for column
|
||||||
/// calculation.
|
/// calculation.
|
||||||
pub fn with_prefix(prefix: &str, src: &'s str, mode: TokenMode) -> Self {
|
pub fn with_prefix(prefix: &str, text: &'s str, mode: TokenMode) -> Self {
|
||||||
let mut tokens = Tokens::with_prefix(prefix, src, mode);
|
let mut tokens = Tokens::with_prefix(prefix, text, mode);
|
||||||
let current = tokens.next();
|
let current = tokens.next();
|
||||||
Self {
|
Self {
|
||||||
tokens,
|
tokens,
|
||||||
|
@ -23,7 +23,7 @@ pub fn resolve_string(string: &str) -> EcoString {
|
|||||||
Some('r') => out.push('\r'),
|
Some('r') => out.push('\r'),
|
||||||
Some('t') => out.push('\t'),
|
Some('t') => out.push('\t'),
|
||||||
Some('u') if s.eat_if('{') => {
|
Some('u') if s.eat_if('{') => {
|
||||||
// TODO: Feedback if closing brace is missing.
|
// TODO: Error if closing brace is missing.
|
||||||
let sequence = s.eat_while(char::is_ascii_hexdigit);
|
let sequence = s.eat_while(char::is_ascii_hexdigit);
|
||||||
let _terminated = s.eat_if('}');
|
let _terminated = s.eat_if('}');
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ pub fn resolve_string(string: &str) -> EcoString {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Feedback about invalid escape sequence.
|
// TODO: Error for invalid escape sequence.
|
||||||
_ => out.push_str(s.from(start)),
|
_ => out.push_str(s.from(start)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,16 +34,16 @@ pub enum TokenMode {
|
|||||||
impl<'s> Tokens<'s> {
|
impl<'s> Tokens<'s> {
|
||||||
/// Create a new token iterator with the given mode.
|
/// Create a new token iterator with the given mode.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(src: &'s str, mode: TokenMode) -> Self {
|
pub fn new(text: &'s str, mode: TokenMode) -> Self {
|
||||||
Self::with_prefix("", src, mode)
|
Self::with_prefix("", text, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new token iterator with the given mode and a prefix to offset
|
/// Create a new token iterator with the given mode and a prefix to offset
|
||||||
/// column calculations.
|
/// column calculations.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn with_prefix(prefix: &str, src: &'s str, mode: TokenMode) -> Self {
|
pub fn with_prefix(prefix: &str, text: &'s str, mode: TokenMode) -> Self {
|
||||||
Self {
|
Self {
|
||||||
s: Scanner::new(src),
|
s: Scanner::new(text),
|
||||||
mode,
|
mode,
|
||||||
terminated: true,
|
terminated: true,
|
||||||
column_offset: column(prefix, prefix.len(), 0),
|
column_offset: column(prefix, prefix.len(), 0),
|
||||||
@ -770,9 +770,9 @@ mod tests {
|
|||||||
t!(Markup $($tts)*);
|
t!(Markup $($tts)*);
|
||||||
t!(Code $($tts)*);
|
t!(Code $($tts)*);
|
||||||
};
|
};
|
||||||
($mode:ident $([$blocks:literal])?: $src:expr => $($token:expr),*) => {{
|
($mode:ident $([$blocks:literal])?: $text:expr => $($token:expr),*) => {{
|
||||||
// Test without suffix.
|
// Test without suffix.
|
||||||
t!(@$mode: $src => $($token),*);
|
t!(@$mode: $text => $($token),*);
|
||||||
|
|
||||||
// Suffixes described by four-tuples of:
|
// Suffixes described by four-tuples of:
|
||||||
//
|
//
|
||||||
@ -810,21 +810,21 @@ mod tests {
|
|||||||
|
|
||||||
// Test with each applicable suffix.
|
// Test with each applicable suffix.
|
||||||
for &(block, mode, suffix, ref token) in suffixes {
|
for &(block, mode, suffix, ref token) in suffixes {
|
||||||
let src = $src;
|
let text = $text;
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
let blocks = BLOCKS;
|
let blocks = BLOCKS;
|
||||||
$(let blocks = $blocks;)?
|
$(let blocks = $blocks;)?
|
||||||
assert!(!blocks.contains(|c| !BLOCKS.contains(c)));
|
assert!(!blocks.contains(|c| !BLOCKS.contains(c)));
|
||||||
if (mode.is_none() || mode == Some($mode)) && blocks.contains(block) {
|
if (mode.is_none() || mode == Some($mode)) && blocks.contains(block) {
|
||||||
t!(@$mode: format!("{}{}", src, suffix) => $($token,)* token);
|
t!(@$mode: format!("{}{}", text, suffix) => $($token,)* token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
(@$mode:ident: $src:expr => $($token:expr),*) => {{
|
(@$mode:ident: $text:expr => $($token:expr),*) => {{
|
||||||
let src = $src;
|
let text = $text;
|
||||||
let found = Tokens::new(&src, $mode).collect::<Vec<_>>();
|
let found = Tokens::new(&text, $mode).collect::<Vec<_>>();
|
||||||
let expected = vec![$($token.clone()),*];
|
let expected = vec![$($token.clone()),*];
|
||||||
check(&src, found, expected);
|
check(&text, found, expected);
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
278
src/source.rs
278
src/source.rs
@ -1,158 +1,16 @@
|
|||||||
//! Source file management.
|
//! Source file management.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::io;
|
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use unscanny::Scanner;
|
use unscanny::Scanner;
|
||||||
|
|
||||||
use crate::diag::{failed_to_load, StrResult, TypResult};
|
use crate::diag::TypResult;
|
||||||
use crate::loading::{FileHash, Loader};
|
|
||||||
use crate::parse::{is_newline, parse, reparse};
|
use crate::parse::{is_newline, parse, reparse};
|
||||||
use crate::syntax::ast::Markup;
|
use crate::syntax::ast::Markup;
|
||||||
use crate::syntax::{Span, SyntaxNode};
|
use crate::syntax::{Span, SyntaxNode};
|
||||||
use crate::util::{PathExt, StrExt};
|
use crate::util::{PathExt, StrExt};
|
||||||
|
|
||||||
#[cfg(feature = "codespan-reporting")]
|
|
||||||
use codespan_reporting::files::{self, Files};
|
|
||||||
|
|
||||||
/// A unique identifier for a loaded source file.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub struct SourceId(u16);
|
|
||||||
|
|
||||||
impl SourceId {
|
|
||||||
/// Create a new source id for a file that is not part of a store.
|
|
||||||
pub const fn detached() -> Self {
|
|
||||||
Self(u16::MAX)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a source id from the raw underlying value.
|
|
||||||
///
|
|
||||||
/// This should only be called with values returned by
|
|
||||||
/// [`into_raw`](Self::into_raw).
|
|
||||||
pub const fn from_raw(v: u16) -> Self {
|
|
||||||
Self(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert into the raw underlying value.
|
|
||||||
pub const fn into_raw(self) -> u16 {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Storage for loaded source files.
|
|
||||||
pub struct SourceStore {
|
|
||||||
loader: Arc<dyn Loader>,
|
|
||||||
files: HashMap<FileHash, SourceId>,
|
|
||||||
sources: Vec<Source>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SourceStore {
|
|
||||||
/// Create a new, empty source store.
|
|
||||||
pub fn new(loader: Arc<dyn Loader>) -> Self {
|
|
||||||
Self {
|
|
||||||
loader,
|
|
||||||
files: HashMap::new(),
|
|
||||||
sources: vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a reference to a loaded source file.
|
|
||||||
///
|
|
||||||
/// This panics if no source file with this `id` exists. This function
|
|
||||||
/// should only be called with ids returned by this store's
|
|
||||||
/// [`load()`](Self::load) and [`provide()`](Self::provide) methods.
|
|
||||||
#[track_caller]
|
|
||||||
pub fn get(&self, id: SourceId) -> &Source {
|
|
||||||
&self.sources[id.0 as usize]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load a source file from a path relative to the compilation environment's
|
|
||||||
/// root.
|
|
||||||
///
|
|
||||||
/// If there already exists a source file for this path, it is
|
|
||||||
/// [replaced](Source::replace).
|
|
||||||
pub fn load(&mut self, path: &Path) -> StrResult<SourceId> {
|
|
||||||
let mut try_load = || -> io::Result<SourceId> {
|
|
||||||
let hash = self.loader.resolve(path)?;
|
|
||||||
if let Some(&id) = self.files.get(&hash) {
|
|
||||||
return Ok(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = self.loader.file(path)?;
|
|
||||||
let src = String::from_utf8(data.to_vec()).map_err(|_| {
|
|
||||||
io::Error::new(io::ErrorKind::InvalidData, "file is not valid utf-8")
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(self.provide(path, src))
|
|
||||||
};
|
|
||||||
|
|
||||||
try_load().map_err(|err| failed_to_load("source file", path, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Directly provide a source file.
|
|
||||||
///
|
|
||||||
/// The `path` does not need to be [resolvable](Loader::resolve) through the
|
|
||||||
/// `loader`. If it is though, imports that resolve to the same file hash
|
|
||||||
/// will use the inserted file instead of going through [`Loader::file`].
|
|
||||||
///
|
|
||||||
/// If the path is resolvable and points to an existing source file, it is
|
|
||||||
/// [replaced](Source::replace).
|
|
||||||
pub fn provide(&mut self, path: impl AsRef<Path>, src: String) -> SourceId {
|
|
||||||
let path = path.as_ref();
|
|
||||||
let hash = self.loader.resolve(path).ok();
|
|
||||||
|
|
||||||
// Check for existing file and replace if one exists.
|
|
||||||
if let Some(&id) = hash.and_then(|hash| self.files.get(&hash)) {
|
|
||||||
self.replace(id, src);
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No existing file yet, so we allocate a new id.
|
|
||||||
let id = SourceId(self.sources.len() as u16);
|
|
||||||
self.sources.push(Source::new(id, path, src));
|
|
||||||
|
|
||||||
// Register in file map if the path was known to the loader.
|
|
||||||
if let Some(hash) = hash {
|
|
||||||
self.files.insert(hash, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
id
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fully [replace](Source::replace) the source text of a file.
|
|
||||||
///
|
|
||||||
/// This panics if no source file with this `id` exists.
|
|
||||||
#[track_caller]
|
|
||||||
pub fn replace(&mut self, id: SourceId, src: String) {
|
|
||||||
self.sources[id.0 as usize].replace(src)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// [Edit](Source::edit) a source file by replacing the given range.
|
|
||||||
///
|
|
||||||
/// This panics if no source file with this `id` exists or if the `replace`
|
|
||||||
/// range is out of bounds.
|
|
||||||
#[track_caller]
|
|
||||||
pub fn edit(
|
|
||||||
&mut self,
|
|
||||||
id: SourceId,
|
|
||||||
replace: Range<usize>,
|
|
||||||
with: &str,
|
|
||||||
) -> Range<usize> {
|
|
||||||
self.sources[id.0 as usize].edit(replace, with)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Map a span that points into a [file](Source::range) stored in this
|
|
||||||
/// source store to a byte range.
|
|
||||||
///
|
|
||||||
/// Panics if the span does not point into this source store.
|
|
||||||
pub fn range(&self, span: Span) -> Range<usize> {
|
|
||||||
self.get(span.source()).range(span)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A single source file.
|
/// A single source file.
|
||||||
///
|
///
|
||||||
/// _Note_: All line and column indices start at zero, just like byte indices.
|
/// _Note_: All line and column indices start at zero, just like byte indices.
|
||||||
@ -160,7 +18,7 @@ impl SourceStore {
|
|||||||
pub struct Source {
|
pub struct Source {
|
||||||
id: SourceId,
|
id: SourceId,
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
src: String,
|
text: String,
|
||||||
lines: Vec<Line>,
|
lines: Vec<Line>,
|
||||||
root: SyntaxNode,
|
root: SyntaxNode,
|
||||||
rev: usize,
|
rev: usize,
|
||||||
@ -168,32 +26,32 @@ pub struct Source {
|
|||||||
|
|
||||||
impl Source {
|
impl Source {
|
||||||
/// Create a new source file.
|
/// Create a new source file.
|
||||||
pub fn new(id: SourceId, path: &Path, src: String) -> Self {
|
pub fn new(id: SourceId, path: &Path, text: String) -> Self {
|
||||||
let lines = std::iter::once(Line { byte_idx: 0, utf16_idx: 0 })
|
let lines = std::iter::once(Line { byte_idx: 0, utf16_idx: 0 })
|
||||||
.chain(lines(0, 0, &src))
|
.chain(lines(0, 0, &text))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut root = parse(&src);
|
let mut root = parse(&text);
|
||||||
root.numberize(id, Span::FULL).unwrap();
|
root.numberize(id, Span::FULL).unwrap();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
path: path.normalize(),
|
path: path.normalize(),
|
||||||
root,
|
root,
|
||||||
src,
|
text,
|
||||||
lines,
|
lines,
|
||||||
rev: 0,
|
rev: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a source file without a real id and path, usually for testing.
|
/// Create a source file without a real id and path, usually for testing.
|
||||||
pub fn detached(src: impl Into<String>) -> Self {
|
pub fn detached(text: impl Into<String>) -> Self {
|
||||||
Self::new(SourceId::detached(), Path::new(""), src.into())
|
Self::new(SourceId::detached(), Path::new(""), text.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a source file with the same synthetic span for all nodes.
|
/// Create a source file with the same synthetic span for all nodes.
|
||||||
pub fn synthesized(src: impl Into<String>, span: Span) -> Self {
|
pub fn synthesized(text: impl Into<String>, span: Span) -> Self {
|
||||||
let mut file = Self::detached(src);
|
let mut file = Self::detached(text);
|
||||||
file.root.synthesize(span);
|
file.root.synthesize(span);
|
||||||
file.id = span.source();
|
file.id = span.source();
|
||||||
file
|
file
|
||||||
@ -225,8 +83,8 @@ impl Source {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The whole source as a string slice.
|
/// The whole source as a string slice.
|
||||||
pub fn src(&self) -> &str {
|
pub fn text(&self) -> &str {
|
||||||
&self.src
|
&self.text
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The revision number of the file.
|
/// The revision number of the file.
|
||||||
@ -239,15 +97,15 @@ impl Source {
|
|||||||
|
|
||||||
/// Slice out the part of the source code enclosed by the range.
|
/// Slice out the part of the source code enclosed by the range.
|
||||||
pub fn get(&self, range: Range<usize>) -> Option<&str> {
|
pub fn get(&self, range: Range<usize>) -> Option<&str> {
|
||||||
self.src.get(range)
|
self.text.get(range)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fully replace the source text and increase the revision number.
|
/// Fully replace the source text and increase the revision number.
|
||||||
pub fn replace(&mut self, src: String) {
|
pub fn replace(&mut self, text: String) {
|
||||||
self.src = src;
|
self.text = text;
|
||||||
self.lines = vec![Line { byte_idx: 0, utf16_idx: 0 }];
|
self.lines = vec![Line { byte_idx: 0, utf16_idx: 0 }];
|
||||||
self.lines.extend(lines(0, 0, &self.src));
|
self.lines.extend(lines(0, 0, &self.text));
|
||||||
self.root = parse(&self.src);
|
self.root = parse(&self.text);
|
||||||
self.root.numberize(self.id(), Span::FULL).unwrap();
|
self.root.numberize(self.id(), Span::FULL).unwrap();
|
||||||
self.rev = self.rev.wrapping_add(1);
|
self.rev = self.rev.wrapping_add(1);
|
||||||
}
|
}
|
||||||
@ -263,34 +121,34 @@ impl Source {
|
|||||||
|
|
||||||
let start_byte = replace.start;
|
let start_byte = replace.start;
|
||||||
let start_utf16 = self.byte_to_utf16(replace.start).unwrap();
|
let start_utf16 = self.byte_to_utf16(replace.start).unwrap();
|
||||||
self.src.replace_range(replace.clone(), with);
|
self.text.replace_range(replace.clone(), with);
|
||||||
|
|
||||||
// Remove invalidated line starts.
|
// Remove invalidated line starts.
|
||||||
let line = self.byte_to_line(start_byte).unwrap();
|
let line = self.byte_to_line(start_byte).unwrap();
|
||||||
self.lines.truncate(line + 1);
|
self.lines.truncate(line + 1);
|
||||||
|
|
||||||
// Handle adjoining of \r and \n.
|
// Handle adjoining of \r and \n.
|
||||||
if self.src[.. start_byte].ends_with('\r') && with.starts_with('\n') {
|
if self.text[.. start_byte].ends_with('\r') && with.starts_with('\n') {
|
||||||
self.lines.pop();
|
self.lines.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recalculate the line starts after the edit.
|
// Recalculate the line starts after the edit.
|
||||||
self.lines
|
self.lines
|
||||||
.extend(lines(start_byte, start_utf16, &self.src[start_byte ..]));
|
.extend(lines(start_byte, start_utf16, &self.text[start_byte ..]));
|
||||||
|
|
||||||
// Incrementally reparse the replaced range.
|
// Incrementally reparse the replaced range.
|
||||||
reparse(&mut self.root, &self.src, replace, with.len())
|
reparse(&mut self.root, &self.text, replace, with.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the length of the file in UTF-8 encoded bytes.
|
/// Get the length of the file in UTF-8 encoded bytes.
|
||||||
pub fn len_bytes(&self) -> usize {
|
pub fn len_bytes(&self) -> usize {
|
||||||
self.src.len()
|
self.text.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the length of the file in UTF-16 code units.
|
/// Get the length of the file in UTF-16 code units.
|
||||||
pub fn len_utf16(&self) -> usize {
|
pub fn len_utf16(&self) -> usize {
|
||||||
let last = self.lines.last().unwrap();
|
let last = self.lines.last().unwrap();
|
||||||
last.utf16_idx + self.src[last.byte_idx ..].len_utf16()
|
last.utf16_idx + self.text[last.byte_idx ..].len_utf16()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the length of the file in lines.
|
/// Get the length of the file in lines.
|
||||||
@ -311,13 +169,13 @@ impl Source {
|
|||||||
pub fn byte_to_utf16(&self, byte_idx: usize) -> Option<usize> {
|
pub fn byte_to_utf16(&self, byte_idx: usize) -> Option<usize> {
|
||||||
let line_idx = self.byte_to_line(byte_idx)?;
|
let line_idx = self.byte_to_line(byte_idx)?;
|
||||||
let line = self.lines.get(line_idx)?;
|
let line = self.lines.get(line_idx)?;
|
||||||
let head = self.src.get(line.byte_idx .. byte_idx)?;
|
let head = self.text.get(line.byte_idx .. byte_idx)?;
|
||||||
Some(line.utf16_idx + head.len_utf16())
|
Some(line.utf16_idx + head.len_utf16())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the index of the line that contains the given byte index.
|
/// Return the index of the line that contains the given byte index.
|
||||||
pub fn byte_to_line(&self, byte_idx: usize) -> Option<usize> {
|
pub fn byte_to_line(&self, byte_idx: usize) -> Option<usize> {
|
||||||
(byte_idx <= self.src.len()).then(|| {
|
(byte_idx <= self.text.len()).then(|| {
|
||||||
match self.lines.binary_search_by_key(&byte_idx, |line| line.byte_idx) {
|
match self.lines.binary_search_by_key(&byte_idx, |line| line.byte_idx) {
|
||||||
Ok(i) => i,
|
Ok(i) => i,
|
||||||
Err(i) => i - 1,
|
Err(i) => i - 1,
|
||||||
@ -346,14 +204,14 @@ impl Source {
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut k = line.utf16_idx;
|
let mut k = line.utf16_idx;
|
||||||
for (i, c) in self.src[line.byte_idx ..].char_indices() {
|
for (i, c) in self.text[line.byte_idx ..].char_indices() {
|
||||||
if k >= utf16_idx {
|
if k >= utf16_idx {
|
||||||
return Some(line.byte_idx + i);
|
return Some(line.byte_idx + i);
|
||||||
}
|
}
|
||||||
k += c.len_utf16();
|
k += c.len_utf16();
|
||||||
}
|
}
|
||||||
|
|
||||||
(k == utf16_idx).then(|| self.src.len())
|
(k == utf16_idx).then(|| self.text.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -365,7 +223,7 @@ impl Source {
|
|||||||
/// Return the range which encloses the given line.
|
/// Return the range which encloses the given line.
|
||||||
pub fn line_to_range(&self, line_idx: usize) -> Option<Range<usize>> {
|
pub fn line_to_range(&self, line_idx: usize) -> Option<Range<usize>> {
|
||||||
let start = self.line_to_byte(line_idx)?;
|
let start = self.line_to_byte(line_idx)?;
|
||||||
let end = self.line_to_byte(line_idx + 1).unwrap_or(self.src.len());
|
let end = self.line_to_byte(line_idx + 1).unwrap_or(self.text.len());
|
||||||
Some(start .. end)
|
Some(start .. end)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -388,6 +246,30 @@ impl Source {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A unique identifier for a loaded source file.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct SourceId(u16);
|
||||||
|
|
||||||
|
impl SourceId {
|
||||||
|
/// Create a new source id for a file that is not part of a store.
|
||||||
|
pub const fn detached() -> Self {
|
||||||
|
Self(u16::MAX)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a source id from the raw underlying value.
|
||||||
|
///
|
||||||
|
/// This should only be called with values returned by
|
||||||
|
/// [`into_raw`](Self::into_raw).
|
||||||
|
pub const fn from_raw(v: u16) -> Self {
|
||||||
|
Self(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert into the raw underlying value.
|
||||||
|
pub const fn into_raw(self) -> u16 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Metadata about a line.
|
/// Metadata about a line.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
struct Line {
|
struct Line {
|
||||||
@ -401,9 +283,9 @@ struct Line {
|
|||||||
fn lines(
|
fn lines(
|
||||||
byte_offset: usize,
|
byte_offset: usize,
|
||||||
utf16_offset: usize,
|
utf16_offset: usize,
|
||||||
string: &str,
|
text: &str,
|
||||||
) -> impl Iterator<Item = Line> + '_ {
|
) -> impl Iterator<Item = Line> + '_ {
|
||||||
let mut s = Scanner::new(string);
|
let mut s = Scanner::new(text);
|
||||||
let mut utf16_idx = utf16_offset;
|
let mut utf16_idx = utf16_offset;
|
||||||
|
|
||||||
std::iter::from_fn(move || {
|
std::iter::from_fn(move || {
|
||||||
@ -427,56 +309,6 @@ fn lines(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "codespan-reporting")]
|
|
||||||
impl<'a> Files<'a> for SourceStore {
|
|
||||||
type FileId = SourceId;
|
|
||||||
type Name = std::path::Display<'a>;
|
|
||||||
type Source = &'a str;
|
|
||||||
|
|
||||||
fn name(&'a self, id: SourceId) -> Result<Self::Name, files::Error> {
|
|
||||||
Ok(self.get(id).path().display())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn source(&'a self, id: SourceId) -> Result<Self::Source, files::Error> {
|
|
||||||
Ok(self.get(id).src())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn line_index(&'a self, id: SourceId, given: usize) -> Result<usize, files::Error> {
|
|
||||||
let source = self.get(id);
|
|
||||||
source
|
|
||||||
.byte_to_line(given)
|
|
||||||
.ok_or_else(|| files::Error::IndexTooLarge { given, max: source.len_bytes() })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn line_range(
|
|
||||||
&'a self,
|
|
||||||
id: SourceId,
|
|
||||||
given: usize,
|
|
||||||
) -> Result<std::ops::Range<usize>, files::Error> {
|
|
||||||
let source = self.get(id);
|
|
||||||
source
|
|
||||||
.line_to_range(given)
|
|
||||||
.ok_or_else(|| files::Error::LineTooLarge { given, max: source.len_lines() })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn column_number(
|
|
||||||
&'a self,
|
|
||||||
id: SourceId,
|
|
||||||
_: usize,
|
|
||||||
given: usize,
|
|
||||||
) -> Result<usize, files::Error> {
|
|
||||||
let source = self.get(id);
|
|
||||||
source.byte_to_column(given).ok_or_else(|| {
|
|
||||||
let max = source.len_bytes();
|
|
||||||
if given <= max {
|
|
||||||
files::Error::InvalidCharBoundary { given }
|
|
||||||
} else {
|
|
||||||
files::Error::IndexTooLarge { given, max }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -563,7 +395,7 @@ mod tests {
|
|||||||
let mut source = Source::detached(prev);
|
let mut source = Source::detached(prev);
|
||||||
let result = Source::detached(after);
|
let result = Source::detached(after);
|
||||||
source.edit(range, with);
|
source.edit(range, with);
|
||||||
assert_eq!(source.src, result.src);
|
assert_eq!(source.text, result.text);
|
||||||
assert_eq!(source.root, result.root);
|
assert_eq!(source.root, result.root);
|
||||||
assert_eq!(source.lines, result.lines);
|
assert_eq!(source.lines, result.lines);
|
||||||
}
|
}
|
||||||
|
@ -384,10 +384,10 @@ mod tests {
|
|||||||
use Category::*;
|
use Category::*;
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn test(src: &str, goal: &[(Range<usize>, Category)]) {
|
fn test(text: &str, goal: &[(Range<usize>, Category)]) {
|
||||||
let mut vec = vec![];
|
let mut vec = vec![];
|
||||||
let source = Source::detached(src);
|
let source = Source::detached(text);
|
||||||
let full = 0 .. src.len();
|
let full = 0 .. text.len();
|
||||||
highlight_node(source.root(), full, &mut |range, category| {
|
highlight_node(source.root(), full, &mut |range, category| {
|
||||||
vec.push((range, category));
|
vec.push((range, category));
|
||||||
});
|
});
|
||||||
|
@ -980,7 +980,7 @@ impl NodeKind {
|
|||||||
Self::LineComment => "line comment",
|
Self::LineComment => "line comment",
|
||||||
Self::BlockComment => "block comment",
|
Self::BlockComment => "block comment",
|
||||||
Self::Error(_, _) => "parse error",
|
Self::Error(_, _) => "parse error",
|
||||||
Self::Unknown(src) => match src.as_str() {
|
Self::Unknown(text) => match text.as_str() {
|
||||||
"*/" => "end of block comment",
|
"*/" => "end of block comment",
|
||||||
_ => "invalid token",
|
_ => "invalid token",
|
||||||
},
|
},
|
||||||
@ -1107,7 +1107,7 @@ impl Hash for NodeKind {
|
|||||||
Self::LineComment => {}
|
Self::LineComment => {}
|
||||||
Self::BlockComment => {}
|
Self::BlockComment => {}
|
||||||
Self::Error(pos, msg) => (pos, msg).hash(state),
|
Self::Error(pos, msg) => (pos, msg).hash(state),
|
||||||
Self::Unknown(src) => src.hash(state),
|
Self::Unknown(text) => text.hash(state),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,8 +42,8 @@ impl<T: Debug> Debug for Spanned<T> {
|
|||||||
/// 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](crate::source::SourceStore::range)
|
/// or element stems from. Can be [mapped back](crate::source::Source::range)
|
||||||
/// to a source id + byte range for user facing display.
|
/// to a byte range for user facing display.
|
||||||
///
|
///
|
||||||
/// Span ids are ordered in the tree to enable quickly finding the node with
|
/// Span ids are ordered in the tree to enable quickly finding the node with
|
||||||
/// some id:
|
/// some id:
|
||||||
|
59
src/util/buffer.rs
Normal file
59
src/util/buffer.rs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use super::Prehashed;
|
||||||
|
|
||||||
|
/// A shared buffer that is cheap to clone and hash.
|
||||||
|
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||||
|
pub struct Buffer(Prehashed<Arc<Vec<u8>>>);
|
||||||
|
|
||||||
|
impl Buffer {
|
||||||
|
/// Return a view into the buffer.
|
||||||
|
pub fn as_slice(&self) -> &[u8] {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a copy of the buffer as a vector.
|
||||||
|
pub fn to_vec(&self) -> Vec<u8> {
|
||||||
|
self.0.to_vec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&[u8]> for Buffer {
|
||||||
|
fn from(slice: &[u8]) -> Self {
|
||||||
|
Self(Prehashed::new(Arc::new(slice.to_vec())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<u8>> for Buffer {
|
||||||
|
fn from(vec: Vec<u8>) -> Self {
|
||||||
|
Self(Prehashed::new(Arc::new(vec)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Arc<Vec<u8>>> for Buffer {
|
||||||
|
fn from(arc: Arc<Vec<u8>>) -> Self {
|
||||||
|
Self(Prehashed::new(arc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Buffer {
|
||||||
|
type Target = [u8];
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<[u8]> for Buffer {
|
||||||
|
fn as_ref(&self) -> &[u8] {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Buffer {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.pad("Buffer(..)")
|
||||||
|
}
|
||||||
|
}
|
@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod eco;
|
mod eco;
|
||||||
|
mod buffer;
|
||||||
mod hash;
|
mod hash;
|
||||||
|
|
||||||
|
pub use buffer::Buffer;
|
||||||
pub use eco::EcoString;
|
pub use eco::EcoString;
|
||||||
pub use hash::Prehashed;
|
pub use hash::Prehashed;
|
||||||
|
|
||||||
|
@ -21,4 +21,4 @@ Die Tiefe eines Knotens _v_ ist die Länge des eindeutigen Weges von der Wurzel
|
|||||||
zu _v_, und die Höhe von _v_ ist die Länge eines längsten (absteigenden) Weges
|
zu _v_, und die Höhe von _v_ ist die Länge eines längsten (absteigenden) Weges
|
||||||
von _v_ zu einem Blatt. Die Höhe des Baumes ist die Höhe der Wurzel.
|
von _v_ zu einem Blatt. Die Höhe des Baumes ist die Höhe der Wurzel.
|
||||||
|
|
||||||
#align(center, image("/res/graph.png", width: 75%))
|
#align(center, image("../res/graph.png", width: 75%))
|
||||||
|
275
tests/typeset.rs
275
tests/typeset.rs
@ -1,24 +1,31 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::{hash_map::Entry, HashMap};
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::fs;
|
use std::fs::{self, File};
|
||||||
|
use std::hash::Hash;
|
||||||
|
use std::io;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
|
use elsa::FrozenVec;
|
||||||
|
use same_file::Handle;
|
||||||
|
use siphasher::sip128::{Hasher128, SipHasher};
|
||||||
use tiny_skia as sk;
|
use tiny_skia as sk;
|
||||||
use unscanny::Scanner;
|
use unscanny::Scanner;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use typst::eval::{Smart, Value};
|
use typst::eval::{Smart, Value};
|
||||||
|
use typst::font::{Font, FontBook};
|
||||||
use typst::frame::{Element, Frame};
|
use typst::frame::{Element, Frame};
|
||||||
use typst::geom::{Length, RgbaColor, Sides};
|
use typst::geom::{Length, RgbaColor, Sides};
|
||||||
use typst::library::layout::PageNode;
|
use typst::library::layout::PageNode;
|
||||||
use typst::library::text::{TextNode, TextSize};
|
use typst::library::text::{TextNode, TextSize};
|
||||||
use typst::loading::FsLoader;
|
|
||||||
use typst::model::StyleMap;
|
use typst::model::StyleMap;
|
||||||
use typst::source::Source;
|
use typst::source::{Source, SourceId};
|
||||||
use typst::syntax::SyntaxNode;
|
use typst::syntax::SyntaxNode;
|
||||||
use typst::{bail, Config, Context};
|
use typst::util::Buffer;
|
||||||
|
use typst::{bail, Config, World};
|
||||||
|
|
||||||
const TYP_DIR: &str = "./typ";
|
const TYP_DIR: &str = "./typ";
|
||||||
const REF_DIR: &str = "./ref";
|
const REF_DIR: &str = "./ref";
|
||||||
@ -57,46 +64,8 @@ fn main() {
|
|||||||
println!("Running {len} tests");
|
println!("Running {len} tests");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 = StyleMap::new();
|
|
||||||
styles.set(PageNode::WIDTH, Smart::Custom(Length::pt(120.0).into()));
|
|
||||||
styles.set(PageNode::HEIGHT, Smart::Auto);
|
|
||||||
styles.set(
|
|
||||||
PageNode::MARGINS,
|
|
||||||
Sides::splat(Some(Smart::Custom(Length::pt(10.0).into()))),
|
|
||||||
);
|
|
||||||
styles.set(TextNode::SIZE, TextSize(Length::pt(10.0).into()));
|
|
||||||
|
|
||||||
// Hook up helpers into the global scope.
|
|
||||||
let mut std = typst::library::new();
|
|
||||||
std.define("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF));
|
|
||||||
std.define("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF));
|
|
||||||
std.def_fn("test", move |_, args| {
|
|
||||||
let lhs = args.expect::<Value>("left-hand side")?;
|
|
||||||
let rhs = args.expect::<Value>("right-hand side")?;
|
|
||||||
if lhs != rhs {
|
|
||||||
bail!(args.span, "Assertion failed: {:?} != {:?}", lhs, rhs,);
|
|
||||||
}
|
|
||||||
Ok(Value::None)
|
|
||||||
});
|
|
||||||
std.def_fn("print", move |_, args| {
|
|
||||||
print!("> ");
|
|
||||||
for (i, value) in args.all::<Value>()?.into_iter().enumerate() {
|
|
||||||
if i > 0 {
|
|
||||||
print!(", ")
|
|
||||||
}
|
|
||||||
print!("{value:?}");
|
|
||||||
}
|
|
||||||
println!();
|
|
||||||
Ok(Value::None)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create loader and context.
|
// Create loader and context.
|
||||||
let loader = FsLoader::new().with_path(FONT_DIR);
|
let mut world = TestWorld::new(args.print);
|
||||||
let config = Config::builder().std(std).styles(styles).build();
|
|
||||||
let mut ctx = Context::new(Arc::new(loader), config);
|
|
||||||
|
|
||||||
// Run all the tests.
|
// Run all the tests.
|
||||||
let mut ok = 0;
|
let mut ok = 0;
|
||||||
@ -108,12 +77,11 @@ fn main() {
|
|||||||
args.pdf.then(|| Path::new(PDF_DIR).join(path).with_extension("pdf"));
|
args.pdf.then(|| Path::new(PDF_DIR).join(path).with_extension("pdf"));
|
||||||
|
|
||||||
ok += test(
|
ok += test(
|
||||||
&mut ctx,
|
&mut world,
|
||||||
&src_path,
|
&src_path,
|
||||||
&png_path,
|
&png_path,
|
||||||
&ref_path,
|
&ref_path,
|
||||||
pdf_path.as_deref(),
|
pdf_path.as_deref(),
|
||||||
&args.print,
|
|
||||||
) as usize;
|
) as usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,18 +147,166 @@ impl Args {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct TestWorld {
|
||||||
|
config: Config,
|
||||||
|
print: PrintConfig,
|
||||||
|
sources: FrozenVec<Box<Source>>,
|
||||||
|
nav: RefCell<HashMap<PathHash, SourceId>>,
|
||||||
|
book: FontBook,
|
||||||
|
fonts: Vec<Font>,
|
||||||
|
files: RefCell<HashMap<PathHash, Buffer>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestWorld {
|
||||||
|
fn new(print: PrintConfig) -> Self {
|
||||||
|
// 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 = StyleMap::new();
|
||||||
|
styles.set(PageNode::WIDTH, Smart::Custom(Length::pt(120.0).into()));
|
||||||
|
styles.set(PageNode::HEIGHT, Smart::Auto);
|
||||||
|
styles.set(
|
||||||
|
PageNode::MARGINS,
|
||||||
|
Sides::splat(Some(Smart::Custom(Length::pt(10.0).into()))),
|
||||||
|
);
|
||||||
|
styles.set(TextNode::SIZE, TextSize(Length::pt(10.0).into()));
|
||||||
|
|
||||||
|
// Hook up helpers into the global scope.
|
||||||
|
let mut std = typst::library::new();
|
||||||
|
std.define("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF));
|
||||||
|
std.define("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF));
|
||||||
|
std.def_fn("test", move |_, args| {
|
||||||
|
let lhs = args.expect::<Value>("left-hand side")?;
|
||||||
|
let rhs = args.expect::<Value>("right-hand side")?;
|
||||||
|
if lhs != rhs {
|
||||||
|
bail!(args.span, "Assertion failed: {:?} != {:?}", lhs, rhs,);
|
||||||
|
}
|
||||||
|
Ok(Value::None)
|
||||||
|
});
|
||||||
|
std.def_fn("print", move |_, args| {
|
||||||
|
print!("> ");
|
||||||
|
for (i, value) in args.all::<Value>()?.into_iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
print!(", ")
|
||||||
|
}
|
||||||
|
print!("{value:?}");
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
Ok(Value::None)
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut fonts = vec![];
|
||||||
|
for entry in WalkDir::new(FONT_DIR)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
.filter(|entry| entry.file_type().is_file())
|
||||||
|
{
|
||||||
|
let buffer: Buffer = fs::read(entry.path()).unwrap().into();
|
||||||
|
for index in 0 .. ttf_parser::fonts_in_collection(&buffer).unwrap_or(1) {
|
||||||
|
fonts.push(Font::new(buffer.clone(), index).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
config: Config { root: PathBuf::new(), std, styles },
|
||||||
|
print,
|
||||||
|
sources: FrozenVec::new(),
|
||||||
|
nav: RefCell::new(HashMap::new()),
|
||||||
|
book: FontBook::from_fonts(&fonts),
|
||||||
|
fonts,
|
||||||
|
files: RefCell::new(HashMap::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn provide(&mut self, path: &Path, text: String) -> SourceId {
|
||||||
|
let hash = PathHash::new(path).unwrap();
|
||||||
|
if let Some(&id) = self.nav.borrow().get(&hash) {
|
||||||
|
self.sources.as_mut()[id.into_raw() as usize].replace(text);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = SourceId::from_raw(self.sources.len() as u16);
|
||||||
|
let source = Source::new(id, path, text);
|
||||||
|
self.sources.push(Box::new(source));
|
||||||
|
self.nav.borrow_mut().insert(hash, id);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl World for TestWorld {
|
||||||
|
fn config(&self) -> &Config {
|
||||||
|
&self.config
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve(&self, path: &Path) -> io::Result<SourceId> {
|
||||||
|
let hash = PathHash::new(path)?;
|
||||||
|
if let Some(&id) = self.nav.borrow().get(&hash) {
|
||||||
|
return Ok(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = fs::read(path)?;
|
||||||
|
let text = String::from_utf8(data).map_err(|_| {
|
||||||
|
io::Error::new(io::ErrorKind::InvalidData, "file is not valid utf-8")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let id = SourceId::from_raw(self.sources.len() as u16);
|
||||||
|
let source = Source::new(id, path, text);
|
||||||
|
self.sources.push(Box::new(source));
|
||||||
|
self.nav.borrow_mut().insert(hash, id);
|
||||||
|
|
||||||
|
Ok(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn source(&self, id: SourceId) -> &Source {
|
||||||
|
&self.sources[id.into_raw() as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn book(&self) -> &FontBook {
|
||||||
|
&self.book
|
||||||
|
}
|
||||||
|
|
||||||
|
fn font(&self, id: usize) -> io::Result<Font> {
|
||||||
|
Ok(self.fonts[id].clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn file(&self, path: &Path) -> io::Result<Buffer> {
|
||||||
|
let hash = PathHash::new(path)?;
|
||||||
|
Ok(match self.files.borrow_mut().entry(hash) {
|
||||||
|
Entry::Occupied(entry) => entry.get().clone(),
|
||||||
|
Entry::Vacant(entry) => entry.insert(fs::read(path)?.into()).clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A hash that is the same for all paths pointing to the same file.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
struct PathHash(u128);
|
||||||
|
|
||||||
|
impl PathHash {
|
||||||
|
fn new(path: &Path) -> io::Result<Self> {
|
||||||
|
let file = File::open(path)?;
|
||||||
|
if file.metadata()?.is_file() {
|
||||||
|
let handle = Handle::from_file(file)?;
|
||||||
|
let mut state = SipHasher::new();
|
||||||
|
handle.hash(&mut state);
|
||||||
|
Ok(Self(state.finish128().as_u128()))
|
||||||
|
} else {
|
||||||
|
Err(io::ErrorKind::NotFound.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn test(
|
fn test(
|
||||||
ctx: &mut Context,
|
world: &mut TestWorld,
|
||||||
src_path: &Path,
|
src_path: &Path,
|
||||||
png_path: &Path,
|
png_path: &Path,
|
||||||
ref_path: &Path,
|
ref_path: &Path,
|
||||||
pdf_path: Option<&Path>,
|
pdf_path: Option<&Path>,
|
||||||
print: &PrintConfig,
|
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let name = src_path.strip_prefix(TYP_DIR).unwrap_or(src_path);
|
let name = src_path.strip_prefix(TYP_DIR).unwrap_or(src_path);
|
||||||
println!("Testing {}", name.display());
|
println!("Testing {}", name.display());
|
||||||
|
|
||||||
let src = fs::read_to_string(src_path).unwrap();
|
let text = fs::read_to_string(src_path).unwrap();
|
||||||
|
|
||||||
let mut ok = true;
|
let mut ok = true;
|
||||||
let mut frames = vec![];
|
let mut frames = vec![];
|
||||||
@ -199,7 +315,7 @@ fn test(
|
|||||||
let mut compare_ever = false;
|
let mut compare_ever = false;
|
||||||
let mut rng = LinearShift::new();
|
let mut rng = LinearShift::new();
|
||||||
|
|
||||||
let parts: Vec<_> = src.split("\n---").collect();
|
let parts: Vec<_> = text.split("\n---").collect();
|
||||||
for (i, &part) in parts.iter().enumerate() {
|
for (i, &part) in parts.iter().enumerate() {
|
||||||
let is_header = i == 0
|
let is_header = i == 0
|
||||||
&& parts.len() > 1
|
&& parts.len() > 1
|
||||||
@ -214,16 +330,8 @@ fn test(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let (part_ok, compare_here, part_frames) = test_part(
|
let (part_ok, compare_here, part_frames) =
|
||||||
ctx,
|
test_part(world, src_path, part.into(), i, compare_ref, line, &mut rng);
|
||||||
src_path,
|
|
||||||
part.into(),
|
|
||||||
i,
|
|
||||||
compare_ref,
|
|
||||||
line,
|
|
||||||
print,
|
|
||||||
&mut rng,
|
|
||||||
);
|
|
||||||
ok &= part_ok;
|
ok &= part_ok;
|
||||||
compare_ever |= compare_here;
|
compare_ever |= compare_here;
|
||||||
frames.extend(part_frames);
|
frames.extend(part_frames);
|
||||||
@ -239,7 +347,7 @@ fn test(
|
|||||||
fs::write(pdf_path, pdf_data).unwrap();
|
fs::write(pdf_path, pdf_data).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
if print.frames {
|
if world.print.frames {
|
||||||
for frame in &frames {
|
for frame in &frames {
|
||||||
println!("Frame: {:#?}", frame);
|
println!("Frame: {:#?}", frame);
|
||||||
}
|
}
|
||||||
@ -268,7 +376,7 @@ fn test(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
if *print == PrintConfig::default() {
|
if world.print == PrintConfig::default() {
|
||||||
print!("\x1b[1A");
|
print!("\x1b[1A");
|
||||||
}
|
}
|
||||||
println!("Testing {} ✔", name.display());
|
println!("Testing {} ✔", name.display());
|
||||||
@ -278,20 +386,19 @@ fn test(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn test_part(
|
fn test_part(
|
||||||
ctx: &mut Context,
|
world: &mut TestWorld,
|
||||||
src_path: &Path,
|
src_path: &Path,
|
||||||
src: String,
|
text: String,
|
||||||
i: usize,
|
i: usize,
|
||||||
compare_ref: bool,
|
compare_ref: bool,
|
||||||
line: usize,
|
line: usize,
|
||||||
print: &PrintConfig,
|
|
||||||
rng: &mut LinearShift,
|
rng: &mut LinearShift,
|
||||||
) -> (bool, bool, Vec<Frame>) {
|
) -> (bool, bool, Vec<Frame>) {
|
||||||
let mut ok = true;
|
let mut ok = true;
|
||||||
|
|
||||||
let id = ctx.sources.provide(src_path, src);
|
let id = world.provide(src_path, text);
|
||||||
let source = ctx.sources.get(id);
|
let source = world.source(id);
|
||||||
if print.syntax {
|
if world.print.syntax {
|
||||||
println!("Syntax Tree: {:#?}", source.root())
|
println!("Syntax Tree: {:#?}", source.root())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,9 +406,9 @@ fn test_part(
|
|||||||
let compare_ref = local_compare_ref.unwrap_or(compare_ref);
|
let compare_ref = local_compare_ref.unwrap_or(compare_ref);
|
||||||
|
|
||||||
ok &= test_spans(source.root());
|
ok &= test_spans(source.root());
|
||||||
ok &= test_reparse(ctx.sources.get(id).src(), i, rng);
|
ok &= test_reparse(world.source(id).text(), i, rng);
|
||||||
|
|
||||||
let (mut frames, errors) = match typst::typeset(ctx, id) {
|
let (mut frames, errors) = match typst::typeset(world, id) {
|
||||||
Ok(frames) => (frames, vec![]),
|
Ok(frames) => (frames, vec![]),
|
||||||
Err(errors) => (vec![], *errors),
|
Err(errors) => (vec![], *errors),
|
||||||
};
|
};
|
||||||
@ -317,7 +424,7 @@ fn test_part(
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|error| error.span.source() == id)
|
.filter(|error| error.span.source() == id)
|
||||||
.map(|error| {
|
.map(|error| {
|
||||||
let range = ctx.sources.range(error.span);
|
let range = world.source(error.span.source()).range(error.span);
|
||||||
let msg = error.message.replace("\\", "/");
|
let msg = error.message.replace("\\", "/");
|
||||||
(range, msg)
|
(range, msg)
|
||||||
})
|
})
|
||||||
@ -330,7 +437,7 @@ fn test_part(
|
|||||||
println!(" Subtest {i} does not match expected errors. ❌");
|
println!(" Subtest {i} does not match expected errors. ❌");
|
||||||
ok = false;
|
ok = false;
|
||||||
|
|
||||||
let source = ctx.sources.get(id);
|
let source = world.source(id);
|
||||||
for error in errors.iter() {
|
for error in errors.iter() {
|
||||||
if !ref_errors.contains(error) {
|
if !ref_errors.contains(error) {
|
||||||
print!(" Not annotated | ");
|
print!(" Not annotated | ");
|
||||||
@ -353,7 +460,7 @@ fn parse_metadata(source: &Source) -> (Option<bool>, Vec<(Range<usize>, String)>
|
|||||||
let mut compare_ref = None;
|
let mut compare_ref = None;
|
||||||
let mut errors = vec![];
|
let mut errors = vec![];
|
||||||
|
|
||||||
let lines: Vec<_> = source.src().lines().map(str::trim).collect();
|
let lines: Vec<_> = source.text().lines().map(str::trim).collect();
|
||||||
for (i, line) in lines.iter().enumerate() {
|
for (i, line) in lines.iter().enumerate() {
|
||||||
if line.starts_with("// Ref: false") {
|
if line.starts_with("// Ref: false") {
|
||||||
compare_ref = Some(false);
|
compare_ref = Some(false);
|
||||||
@ -409,7 +516,7 @@ fn print_error(source: &Source, line: usize, (range, message): &(Range<usize>, S
|
|||||||
/// The method will first inject 10 strings once every 400 source characters
|
/// The method will first inject 10 strings once every 400 source characters
|
||||||
/// and then select 5 leaf node boundries to inject an additional, randomly
|
/// and then select 5 leaf node boundries to inject an additional, randomly
|
||||||
/// chosen string from the injection list.
|
/// chosen string from the injection list.
|
||||||
fn test_reparse(src: &str, i: usize, rng: &mut LinearShift) -> bool {
|
fn test_reparse(text: &str, i: usize, rng: &mut LinearShift) -> bool {
|
||||||
let supplements = [
|
let supplements = [
|
||||||
"[",
|
"[",
|
||||||
"]",
|
"]",
|
||||||
@ -441,19 +548,19 @@ fn test_reparse(src: &str, i: usize, rng: &mut LinearShift) -> bool {
|
|||||||
let mut ok = true;
|
let mut ok = true;
|
||||||
|
|
||||||
let apply = |replace: std::ops::Range<usize>, with| {
|
let apply = |replace: std::ops::Range<usize>, with| {
|
||||||
let mut incr_source = Source::detached(src);
|
let mut incr_source = Source::detached(text);
|
||||||
if incr_source.root().len() != src.len() {
|
if incr_source.root().len() != text.len() {
|
||||||
println!(
|
println!(
|
||||||
" Subtest {i} tree length {} does not match string length {} ❌",
|
" Subtest {i} tree length {} does not match string length {} ❌",
|
||||||
incr_source.root().len(),
|
incr_source.root().len(),
|
||||||
src.len(),
|
text.len(),
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
incr_source.edit(replace.clone(), with);
|
incr_source.edit(replace.clone(), with);
|
||||||
|
|
||||||
let edited_src = incr_source.src();
|
let edited_src = incr_source.text();
|
||||||
let incr_root = incr_source.root();
|
let incr_root = incr_source.root();
|
||||||
let ref_source = Source::detached(edited_src);
|
let ref_source = Source::detached(edited_src);
|
||||||
let ref_root = ref_source.root();
|
let ref_root = ref_source.root();
|
||||||
@ -481,20 +588,20 @@ fn test_reparse(src: &str, i: usize, rng: &mut LinearShift) -> bool {
|
|||||||
(range.start as f64 + ratio * (range.end - range.start) as f64).floor() as usize
|
(range.start as f64 + ratio * (range.end - range.start) as f64).floor() as usize
|
||||||
};
|
};
|
||||||
|
|
||||||
let insertions = (src.len() as f64 / 400.0).ceil() as usize;
|
let insertions = (text.len() as f64 / 400.0).ceil() as usize;
|
||||||
for _ in 0 .. insertions {
|
for _ in 0 .. insertions {
|
||||||
let supplement = supplements[pick(0 .. supplements.len())];
|
let supplement = supplements[pick(0 .. supplements.len())];
|
||||||
let start = pick(0 .. src.len());
|
let start = pick(0 .. text.len());
|
||||||
let end = pick(start .. src.len());
|
let end = pick(start .. text.len());
|
||||||
|
|
||||||
if !src.is_char_boundary(start) || !src.is_char_boundary(end) {
|
if !text.is_char_boundary(start) || !text.is_char_boundary(end) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok &= apply(start .. end, supplement);
|
ok &= apply(start .. end, supplement);
|
||||||
}
|
}
|
||||||
|
|
||||||
let source = Source::detached(src);
|
let source = Source::detached(text);
|
||||||
let leafs = source.root().leafs();
|
let leafs = source.root().leafs();
|
||||||
let start = source.range(leafs[pick(0 .. leafs.len())].span()).start;
|
let start = source.range(leafs[pick(0 .. leafs.len())].span()).start;
|
||||||
let supplement = supplements[pick(0 .. supplements.len())];
|
let supplement = supplements[pick(0 .. supplements.len())];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user