Remove Tracer (#4365)

This commit is contained in:
Laurenz 2024-06-10 11:00:54 +02:00 committed by GitHub
parent f91cad7d78
commit a68a241570
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 251 additions and 212 deletions

View File

@ -8,8 +8,7 @@ use codespan_reporting::term;
use ecow::{eco_format, eco_vec, EcoString, EcoVec}; use ecow::{eco_format, eco_vec, EcoString, EcoVec};
use parking_lot::RwLock; use parking_lot::RwLock;
use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use typst::diag::{bail, FileError, Severity, SourceDiagnostic, StrResult}; use typst::diag::{bail, FileError, Severity, SourceDiagnostic, StrResult, Warned};
use typst::eval::Tracer;
use typst::foundations::{Datetime, Smart}; use typst::foundations::{Datetime, Smart};
use typst::layout::{Frame, PageRanges}; use typst::layout::{Frame, PageRanges};
use typst::model::Document; use typst::model::Document;
@ -112,11 +111,9 @@ pub fn compile_once(
return Ok(()); return Ok(());
} }
let mut tracer = Tracer::new(); let Warned { output, warnings } = typst::compile(world);
let result = typst::compile(world, &mut tracer);
let warnings = tracer.warnings();
match result { match output {
// Export the PDF / PNG. // Export the PDF / PNG.
Ok(document) => { Ok(document) => {
export(world, &document, command, watching)?; export(world, &document, command, watching)?;

View File

@ -1,8 +1,8 @@
use comemo::Track; use comemo::Track;
use ecow::{eco_format, EcoString}; use ecow::{eco_format, EcoString};
use serde::Serialize; use serde::Serialize;
use typst::diag::{bail, HintedStrResult, StrResult}; use typst::diag::{bail, HintedStrResult, StrResult, Warned};
use typst::eval::{eval_string, EvalMode, Tracer}; use typst::eval::{eval_string, EvalMode};
use typst::foundations::{Content, IntoValue, LocatableSelector, Scope}; use typst::foundations::{Content, IntoValue, LocatableSelector, Scope};
use typst::model::Document; use typst::model::Document;
use typst::syntax::Span; use typst::syntax::Span;
@ -21,11 +21,9 @@ pub fn query(command: &QueryCommand) -> HintedStrResult<()> {
world.reset(); world.reset();
world.source(world.main()).map_err(|err| err.to_string())?; world.source(world.main()).map_err(|err| err.to_string())?;
let mut tracer = Tracer::new(); let Warned { output, warnings } = typst::compile(&world);
let result = typst::compile(&world, &mut tracer);
let warnings = tracer.warnings();
match result { match output {
// Retrieve and print query results. // Retrieve and print query results.
Ok(document) => { Ok(document) => {
let data = retrieve(&world, command, &document)?; let data = retrieve(&world, command, &document)?;

View File

@ -1,7 +1,7 @@
use comemo::Track; use comemo::Track;
use ecow::{eco_vec, EcoString, EcoVec}; use ecow::{eco_vec, EcoString, EcoVec};
use typst::engine::{Engine, Route}; use typst::engine::{Engine, Route, Sink, Traced};
use typst::eval::{Tracer, Vm}; use typst::eval::Vm;
use typst::foundations::{Context, Label, Scopes, Styles, Value}; use typst::foundations::{Context, Label, Scopes, Styles, Value};
use typst::introspection::Introspector; use typst::introspection::Introspector;
use typst::model::{BibliographyElem, Document}; use typst::model::{BibliographyElem, Document};
@ -38,10 +38,7 @@ pub fn analyze_expr(
} }
} }
let mut tracer = Tracer::new(); return typst::trace(world, node.span());
tracer.inspect(node.span());
typst::compile(world, &mut tracer).ok();
return tracer.values();
} }
}; };
@ -59,12 +56,14 @@ pub fn analyze_import(world: &dyn World, source: &LinkedNode) -> Option<Value> {
} }
let introspector = Introspector::default(); let introspector = Introspector::default();
let mut tracer = Tracer::new(); let traced = Traced::default();
let mut sink = Sink::new();
let engine = Engine { let engine = Engine {
world: world.track(), world: world.track(),
route: Route::default(),
introspector: introspector.track(), introspector: introspector.track(),
tracer: tracer.track_mut(), traced: traced.track(),
sink: sink.track_mut(),
route: Route::default(),
}; };
let context = Context::none(); let context = Context::none();

View File

@ -1406,7 +1406,6 @@ impl<'a> CompletionContext<'a> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use typst::eval::Tracer;
use super::autocomplete; use super::autocomplete;
use crate::tests::TestWorld; use crate::tests::TestWorld;
@ -1414,7 +1413,7 @@ mod tests {
#[track_caller] #[track_caller]
fn test(text: &str, cursor: usize, contains: &[&str], excludes: &[&str]) { fn test(text: &str, cursor: usize, contains: &[&str], excludes: &[&str]) {
let world = TestWorld::new(text); let world = TestWorld::new(text);
let doc = typst::compile(&world, &mut Tracer::new()).ok(); let doc = typst::compile(&world).output.ok();
let (_, completions) = let (_, completions) =
autocomplete(&world, doc.as_ref(), &world.main, cursor, true) autocomplete(&world, doc.as_ref(), &world.main, cursor, true)
.unwrap_or_default(); .unwrap_or_default();

View File

@ -170,7 +170,6 @@ fn is_in_rect(pos: Point, size: Size, click: Point) -> bool {
mod tests { mod tests {
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
use typst::eval::Tracer;
use typst::layout::{Abs, Point, Position}; use typst::layout::{Abs, Point, Position};
use super::{jump_from_click, jump_from_cursor, Jump}; use super::{jump_from_click, jump_from_cursor, Jump};
@ -200,14 +199,14 @@ mod tests {
#[track_caller] #[track_caller]
fn test_click(text: &str, click: Point, expected: Option<Jump>) { fn test_click(text: &str, click: Point, expected: Option<Jump>) {
let world = TestWorld::new(text); let world = TestWorld::new(text);
let doc = typst::compile(&world, &mut Tracer::new()).unwrap(); let doc = typst::compile(&world).output.unwrap();
assert_eq!(jump_from_click(&world, &doc, &doc.pages[0].frame, click), expected); assert_eq!(jump_from_click(&world, &doc, &doc.pages[0].frame, click), expected);
} }
#[track_caller] #[track_caller]
fn test_cursor(text: &str, cursor: usize, expected: Option<Position>) { fn test_cursor(text: &str, cursor: usize, expected: Option<Position>) {
let world = TestWorld::new(text); let world = TestWorld::new(text);
let doc = typst::compile(&world, &mut Tracer::new()).unwrap(); let doc = typst::compile(&world).output.unwrap();
let pos = jump_from_cursor(&doc, &world.main, cursor); let pos = jump_from_cursor(&doc, &world.main, cursor);
assert_eq!(pos.is_some(), expected.is_some()); assert_eq!(pos.is_some(), expected.is_some());
if let (Some(pos), Some(expected)) = (pos, expected) { if let (Some(pos), Some(expected)) = (pos, expected) {

View File

@ -2,7 +2,8 @@ use std::fmt::Write;
use ecow::{eco_format, EcoString}; use ecow::{eco_format, EcoString};
use if_chain::if_chain; use if_chain::if_chain;
use typst::eval::{CapturesVisitor, Tracer}; use typst::engine::Sink;
use typst::eval::CapturesVisitor;
use typst::foundations::{repr, Capturer, CastInfo, Repr, Value}; use typst::foundations::{repr, Capturer, CastInfo, Repr, Value};
use typst::layout::Length; use typst::layout::Length;
use typst::model::Document; use typst::model::Document;
@ -79,7 +80,7 @@ fn expr_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<Tooltip> {
let mut last = None; let mut last = None;
let mut pieces: Vec<EcoString> = vec![]; let mut pieces: Vec<EcoString> = vec![];
let mut iter = values.iter(); let mut iter = values.iter();
for (value, _) in (&mut iter).take(Tracer::MAX_VALUES - 1) { for (value, _) in (&mut iter).take(Sink::MAX_VALUES - 1) {
if let Some((prev, count)) = &mut last { if let Some((prev, count)) = &mut last {
if *prev == value { if *prev == value {
*count += 1; *count += 1;
@ -253,7 +254,6 @@ fn font_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<Tooltip> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use typst::eval::Tracer;
use typst::syntax::Side; use typst::syntax::Side;
use super::{tooltip, Tooltip}; use super::{tooltip, Tooltip};
@ -270,7 +270,7 @@ mod tests {
#[track_caller] #[track_caller]
fn test(text: &str, cursor: usize, side: Side, expected: Option<Tooltip>) { fn test(text: &str, cursor: usize, side: Side, expected: Option<Tooltip>) {
let world = TestWorld::new(text); let world = TestWorld::new(text);
let doc = typst::compile(&world, &mut Tracer::new()).ok(); let doc = typst::compile(&world).output.ok();
assert_eq!(tooltip(&world, doc.as_ref(), &world.main, cursor, side), expected); assert_eq!(tooltip(&world, doc.as_ref(), &world.main, cursor, side), expected);
} }

View File

@ -138,6 +138,15 @@ pub use {
/// A result that can carry multiple source errors. /// A result that can carry multiple source errors.
pub type SourceResult<T> = Result<T, EcoVec<SourceDiagnostic>>; pub type SourceResult<T> = Result<T, EcoVec<SourceDiagnostic>>;
/// An output alongside warnings generated while producing it.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Warned<T> {
/// The produced output.
pub output: T,
/// Warnings generated while producing the output.
pub warnings: EcoVec<SourceDiagnostic>,
}
/// An error or warning in a source file. /// An error or warning in a source file.
/// ///
/// The contained spans will only be detached if any of the input source files /// The contained spans will only be detached if any of the input source files

View File

@ -1,13 +1,15 @@
//! Definition of the central compilation context. //! Definition of the central compilation context.
use std::collections::HashSet;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use comemo::{Track, Tracked, TrackedMut, Validate}; use comemo::{Track, Tracked, TrackedMut, Validate};
use ecow::EcoVec;
use crate::diag::SourceResult; use crate::diag::{SourceDiagnostic, SourceResult};
use crate::eval::Tracer; use crate::foundations::{Styles, Value};
use crate::introspection::Introspector; use crate::introspection::Introspector;
use crate::syntax::FileId; use crate::syntax::{FileId, Span};
use crate::World; use crate::World;
/// Holds all data needed during compilation. /// Holds all data needed during compilation.
@ -16,18 +18,20 @@ pub struct Engine<'a> {
pub world: Tracked<'a, dyn World + 'a>, pub world: Tracked<'a, dyn World + 'a>,
/// Provides access to information about the document. /// Provides access to information about the document.
pub introspector: Tracked<'a, Introspector>, pub introspector: Tracked<'a, Introspector>,
/// May hold a span that is currently under inspection.
pub traced: Tracked<'a, Traced>,
/// A pure sink for warnings, delayed errors, and spans under inspection.
pub sink: TrackedMut<'a, Sink>,
/// The route the engine took during compilation. This is used to detect /// The route the engine took during compilation. This is used to detect
/// cyclic imports and excessive nesting. /// cyclic imports and excessive nesting.
pub route: Route<'a>, pub route: Route<'a>,
/// The tracer for inspection of the values an expression produces.
pub tracer: TrackedMut<'a, Tracer>,
} }
impl Engine<'_> { impl Engine<'_> {
/// Performs a fallible operation that does not immediately terminate further /// Performs a fallible operation that does not immediately terminate further
/// execution. Instead it produces a delayed error that is only promoted to /// execution. Instead it produces a delayed error that is only promoted to
/// a fatal one if it remains at the end of the introspection loop. /// a fatal one if it remains at the end of the introspection loop.
pub fn delayed<F, T>(&mut self, f: F) -> T pub fn delay<F, T>(&mut self, f: F) -> T
where where
F: FnOnce(&mut Self) -> SourceResult<T>, F: FnOnce(&mut Self) -> SourceResult<T>,
T: Default, T: Default,
@ -35,13 +39,112 @@ impl Engine<'_> {
match f(self) { match f(self) {
Ok(value) => value, Ok(value) => value,
Err(errors) => { Err(errors) => {
self.tracer.delay(errors); self.sink.delay(errors);
T::default() T::default()
} }
} }
} }
} }
/// May hold a span that is currently under inspection.
#[derive(Default)]
pub struct Traced(Option<Span>);
impl Traced {
/// Wraps a to-be-traced `Span`.
///
/// Call `Traced::default()` to trace nothing.
pub fn new(traced: Span) -> Self {
Self(Some(traced))
}
}
#[comemo::track]
impl Traced {
/// Returns the traced span _if_ it is part of the given source file or
/// `None` otherwise.
///
/// We hide the span if it isn't in the given file so that only results for
/// the file with the traced span are invalidated.
pub fn get(&self, id: FileId) -> Option<Span> {
if self.0.and_then(Span::id) == Some(id) {
self.0
} else {
None
}
}
}
/// A push-only sink for delayed errors, warnings, and traced values.
///
/// All tracked methods of this type are of the form `(&mut self, ..) -> ()`, so
/// in principle they do not need validation (though that optimization is not
/// yet implemented in comemo).
#[derive(Default, Clone)]
pub struct Sink {
/// Delayed errors: Those are errors that we can ignore until the last
/// iteration. For instance, show rules may throw during earlier iterations
/// because the introspector is not yet ready. We first ignore that and
/// proceed with empty content and only if the error remains by the end
/// of the last iteration, we promote it.
delayed: EcoVec<SourceDiagnostic>,
/// Warnings emitted during iteration.
warnings: EcoVec<SourceDiagnostic>,
/// Hashes of all warning's spans and messages for warning deduplication.
warnings_set: HashSet<u128>,
/// A sequence of traced values for a span.
values: EcoVec<(Value, Option<Styles>)>,
}
impl Sink {
/// The maximum number of traced values.
pub const MAX_VALUES: usize = 10;
/// Create a new empty sink.
pub fn new() -> Self {
Self::default()
}
/// Get the stored delayed errors.
pub fn delayed(&mut self) -> EcoVec<SourceDiagnostic> {
std::mem::take(&mut self.delayed)
}
/// Get the stored warnings.
pub fn warnings(self) -> EcoVec<SourceDiagnostic> {
self.warnings
}
/// Get the values for the traced span.
pub fn values(self) -> EcoVec<(Value, Option<Styles>)> {
self.values
}
}
#[comemo::track]
impl Sink {
/// Push delayed errors.
pub fn delay(&mut self, errors: EcoVec<SourceDiagnostic>) {
self.delayed.extend(errors);
}
/// Add a warning.
pub fn warn(&mut self, warning: SourceDiagnostic) {
// Check if warning is a duplicate.
let hash = crate::utils::hash128(&(&warning.span, &warning.message));
if self.warnings_set.insert(hash) {
self.warnings.push(warning);
}
}
/// Trace a value and optionally styles for the traced span.
pub fn value(&mut self, value: Value, styles: Option<Styles>) {
if self.values.len() < Self::MAX_VALUES {
self.values.push((value, styles));
}
}
}
/// The route the engine took during compilation. This is used to detect /// The route the engine took during compilation. This is used to detect
/// cyclic imports and excessive nesting. /// cyclic imports and excessive nesting.
pub struct Route<'a> { pub struct Route<'a> {

View File

@ -2,8 +2,8 @@ use comemo::{Tracked, TrackedMut};
use ecow::{eco_format, EcoVec}; use ecow::{eco_format, EcoVec};
use crate::diag::{bail, error, At, HintedStrResult, SourceResult, Trace, Tracepoint}; use crate::diag::{bail, error, At, HintedStrResult, SourceResult, Trace, Tracepoint};
use crate::engine::Engine; use crate::engine::{Engine, Sink, Traced};
use crate::eval::{Access, Eval, FlowEvent, Route, Tracer, Vm}; use crate::eval::{Access, Eval, FlowEvent, Route, Vm};
use crate::foundations::{ use crate::foundations::{
call_method_mut, is_mutating_method, Arg, Args, Bytes, Capturer, Closure, Content, call_method_mut, is_mutating_method, Arg, Args, Bytes, Capturer, Closure, Content,
Context, Func, IntoValue, NativeElement, Scope, Scopes, Value, Context, Func, IntoValue, NativeElement, Scope, Scopes, Value,
@ -275,8 +275,9 @@ pub(crate) fn call_closure(
closure: &LazyHash<Closure>, closure: &LazyHash<Closure>,
world: Tracked<dyn World + '_>, world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>, introspector: Tracked<Introspector>,
traced: Tracked<Traced>,
sink: TrackedMut<Sink>,
route: Tracked<Route>, route: Tracked<Route>,
tracer: TrackedMut<Tracer>,
context: Tracked<Context>, context: Tracked<Context>,
mut args: Args, mut args: Args,
) -> SourceResult<Value> { ) -> SourceResult<Value> {
@ -294,8 +295,9 @@ pub(crate) fn call_closure(
let engine = Engine { let engine = Engine {
world, world,
introspector, introspector,
traced,
sink,
route: Route::extend(route), route: Route::extend(route),
tracer,
}; };
// Prepare VM. // Prepare VM.

View File

@ -35,7 +35,7 @@ impl Eval for ast::ModuleImport<'_> {
if let ast::Expr::Ident(ident) = self.source() { if let ast::Expr::Ident(ident) = self.source() {
if ident.as_str() == new_name.as_str() { if ident.as_str() == new_name.as_str() {
// Warn on `import x as x` // Warn on `import x as x`
vm.engine.tracer.warn(warning!( vm.engine.sink.warn(warning!(
new_name.span(), new_name.span(),
"unnecessary import rename to same name", "unnecessary import rename to same name",
)); ));
@ -110,7 +110,7 @@ impl Eval for ast::ModuleImport<'_> {
if renamed_item.original_name().as_str() if renamed_item.original_name().as_str()
== renamed_item.new_name().as_str() == renamed_item.new_name().as_str()
{ {
vm.engine.tracer.warn(warning!( vm.engine.sink.warn(warning!(
renamed_item.new_name().span(), renamed_item.new_name().span(),
"unnecessary import rename to same name", "unnecessary import rename to same name",
)); ));
@ -185,8 +185,9 @@ fn import_package(vm: &mut Vm, spec: PackageSpec, span: Span) -> SourceResult<Mo
let point = || Tracepoint::Import; let point = || Tracepoint::Import;
Ok(eval( Ok(eval(
vm.world(), vm.world(),
vm.engine.traced,
TrackedMut::reborrow_mut(&mut vm.engine.sink),
vm.engine.route.track(), vm.engine.route.track(),
TrackedMut::reborrow_mut(&mut vm.engine.tracer),
&source, &source,
) )
.trace(vm.world(), point, span)? .trace(vm.world(), point, span)?
@ -209,8 +210,9 @@ fn import_file(vm: &mut Vm, path: &str, span: Span) -> SourceResult<Module> {
let point = || Tracepoint::Import; let point = || Tracepoint::Import;
eval( eval(
world, world,
vm.engine.traced,
TrackedMut::reborrow_mut(&mut vm.engine.sink),
vm.engine.route.track(), vm.engine.route.track(),
TrackedMut::reborrow_mut(&mut vm.engine.tracer),
&source, &source,
) )
.trace(world, point, span) .trace(world, point, span)

View File

@ -134,7 +134,7 @@ impl Eval for ast::Strong<'_> {
let body = self.body(); let body = self.body();
if body.exprs().next().is_none() { if body.exprs().next().is_none() {
vm.engine vm.engine
.tracer .sink
.warn(warning!( .warn(warning!(
self.span(), "no text within stars"; self.span(), "no text within stars";
hint: "using multiple consecutive stars (e.g. **) has no additional effect", hint: "using multiple consecutive stars (e.g. **) has no additional effect",
@ -152,7 +152,7 @@ impl Eval for ast::Emph<'_> {
let body = self.body(); let body = self.body();
if body.exprs().next().is_none() { if body.exprs().next().is_none() {
vm.engine vm.engine
.tracer .sink
.warn(warning!( .warn(warning!(
self.span(), "no text within underscores"; self.span(), "no text within underscores";
hint: "using multiple consecutive underscores (e.g. __) has no additional effect" hint: "using multiple consecutive underscores (e.g. __) has no additional effect"

View File

@ -11,12 +11,10 @@ mod import;
mod markup; mod markup;
mod math; mod math;
mod rules; mod rules;
mod tracer;
mod vm; mod vm;
pub use self::call::*; pub use self::call::*;
pub use self::import::*; pub use self::import::*;
pub use self::tracer::*;
pub use self::vm::*; pub use self::vm::*;
pub(crate) use self::access::*; pub(crate) use self::access::*;
@ -26,7 +24,7 @@ pub(crate) use self::flow::*;
use comemo::{Track, Tracked, TrackedMut}; use comemo::{Track, Tracked, TrackedMut};
use crate::diag::{bail, SourceResult}; use crate::diag::{bail, SourceResult};
use crate::engine::{Engine, Route}; use crate::engine::{Engine, Route, Sink, Traced};
use crate::foundations::{Cast, Context, Module, NativeElement, Scope, Scopes, Value}; use crate::foundations::{Cast, Context, Module, NativeElement, Scope, Scopes, Value};
use crate::introspection::Introspector; use crate::introspection::Introspector;
use crate::math::EquationElem; use crate::math::EquationElem;
@ -38,8 +36,9 @@ use crate::World;
#[typst_macros::time(name = "eval", span = source.root().span())] #[typst_macros::time(name = "eval", span = source.root().span())]
pub fn eval( pub fn eval(
world: Tracked<dyn World + '_>, world: Tracked<dyn World + '_>,
traced: Tracked<Traced>,
sink: TrackedMut<Sink>,
route: Tracked<Route>, route: Tracked<Route>,
tracer: TrackedMut<Tracer>,
source: &Source, source: &Source,
) -> SourceResult<Module> { ) -> SourceResult<Module> {
// Prevent cyclic evaluation. // Prevent cyclic evaluation.
@ -52,9 +51,10 @@ pub fn eval(
let introspector = Introspector::default(); let introspector = Introspector::default();
let engine = Engine { let engine = Engine {
world, world,
route: Route::extend(route).with_id(id),
introspector: introspector.track(), introspector: introspector.track(),
tracer, traced,
sink,
route: Route::extend(route).with_id(id),
}; };
// Prepare VM. // Prepare VM.
@ -115,13 +115,15 @@ pub fn eval_string(
} }
// Prepare the engine. // Prepare the engine.
let mut tracer = Tracer::new(); let mut sink = Sink::new();
let introspector = Introspector::default(); let introspector = Introspector::default();
let traced = Traced::default();
let engine = Engine { let engine = Engine {
world, world,
introspector: introspector.track(), introspector: introspector.track(),
traced: traced.track(),
sink: sink.track_mut(),
route: Route::default(), route: Route::default(),
tracer: tracer.track_mut(),
}; };
// Prepare VM. // Prepare VM.

View File

@ -1,82 +0,0 @@
use std::collections::HashSet;
use ecow::EcoVec;
use crate::diag::SourceDiagnostic;
use crate::foundations::{Styles, Value};
use crate::syntax::{FileId, Span};
use crate::utils::hash128;
/// Traces warnings and which values existed for an expression at a span.
#[derive(Default, Clone)]
pub struct Tracer {
inspected: Option<Span>,
warnings: EcoVec<SourceDiagnostic>,
warnings_set: HashSet<u128>,
delayed: EcoVec<SourceDiagnostic>,
values: EcoVec<(Value, Option<Styles>)>,
}
impl Tracer {
/// The maximum number of inspected values.
pub const MAX_VALUES: usize = 10;
/// Create a new tracer.
pub fn new() -> Self {
Self::default()
}
/// Get the stored delayed errors.
pub fn delayed(&mut self) -> EcoVec<SourceDiagnostic> {
std::mem::take(&mut self.delayed)
}
/// Get the stored warnings.
pub fn warnings(self) -> EcoVec<SourceDiagnostic> {
self.warnings
}
/// Mark a span as inspected. All values observed for this span can be
/// retrieved via `values` later.
pub fn inspect(&mut self, span: Span) {
self.inspected = Some(span);
}
/// Get the values for the inspected span.
pub fn values(self) -> EcoVec<(Value, Option<Styles>)> {
self.values
}
}
#[comemo::track]
impl Tracer {
/// Push delayed errors.
pub fn delay(&mut self, errors: EcoVec<SourceDiagnostic>) {
self.delayed.extend(errors);
}
/// Add a warning.
pub fn warn(&mut self, warning: SourceDiagnostic) {
// Check if warning is a duplicate.
let hash = hash128(&(&warning.span, &warning.message));
if self.warnings_set.insert(hash) {
self.warnings.push(warning);
}
}
/// The inspected span if it is part of the given source file.
pub fn inspected(&self, id: FileId) -> Option<Span> {
if self.inspected.and_then(Span::id) == Some(id) {
self.inspected
} else {
None
}
}
/// Trace a value for the span.
pub fn value(&mut self, value: Value, styles: Option<Styles>) {
if self.values.len() < Self::MAX_VALUES {
self.values.push((value, styles));
}
}
}

View File

@ -32,7 +32,7 @@ impl<'a> Vm<'a> {
scopes: Scopes<'a>, scopes: Scopes<'a>,
target: Span, target: Span,
) -> Self { ) -> Self {
let inspected = target.id().and_then(|id| engine.tracer.inspected(id)); let inspected = target.id().and_then(|id| engine.traced.get(id));
Self { engine, context, flow: None, scopes, inspected } Self { engine, context, flow: None, scopes, inspected }
} }
@ -54,7 +54,7 @@ impl<'a> Vm<'a> {
#[cold] #[cold]
pub fn trace(&mut self, value: Value) { pub fn trace(&mut self, value: Value) {
self.engine self.engine
.tracer .sink
.value(value.clone(), self.context.styles().ok().map(|s| s.to_map())); .value(value.clone(), self.context.styles().ok().map(|s| s.to_map()));
} }
} }

View File

@ -297,8 +297,9 @@ impl Func {
closure, closure,
engine.world, engine.world,
engine.introspector, engine.introspector,
engine.traced,
TrackedMut::reborrow_mut(&mut engine.sink),
engine.route.track(), engine.route.track(),
TrackedMut::reborrow_mut(&mut engine.tracer),
context, context,
args, args,
), ),

View File

@ -6,8 +6,7 @@ use ecow::{eco_format, eco_vec, EcoString, EcoVec};
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use crate::diag::{bail, At, HintedStrResult, SourceResult}; use crate::diag::{bail, At, HintedStrResult, SourceResult};
use crate::engine::{Engine, Route}; use crate::engine::{Engine, Route, Sink, Traced};
use crate::eval::Tracer;
use crate::foundations::{ use crate::foundations::{
cast, elem, func, scope, select_where, ty, Args, Array, Construct, Content, Context, cast, elem, func, scope, select_where, ty, Args, Array, Construct, Content, Context,
Element, Func, IntoValue, Label, LocatableSelector, NativeElement, Packed, Repr, Element, Func, IntoValue, Label, LocatableSelector, NativeElement, Packed, Repr,
@ -281,8 +280,9 @@ impl Counter {
self.sequence_impl( self.sequence_impl(
engine.world, engine.world,
engine.introspector, engine.introspector,
engine.traced,
TrackedMut::reborrow_mut(&mut engine.sink),
engine.route.track(), engine.route.track(),
TrackedMut::reborrow_mut(&mut engine.tracer),
) )
} }
@ -292,14 +292,16 @@ impl Counter {
&self, &self,
world: Tracked<dyn World + '_>, world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>, introspector: Tracked<Introspector>,
traced: Tracked<Traced>,
sink: TrackedMut<Sink>,
route: Tracked<Route>, route: Tracked<Route>,
tracer: TrackedMut<Tracer>,
) -> SourceResult<EcoVec<(CounterState, NonZeroUsize)>> { ) -> SourceResult<EcoVec<(CounterState, NonZeroUsize)>> {
let mut engine = Engine { let mut engine = Engine {
world, world,
introspector, introspector,
traced,
sink,
route: Route::extend(route).unnested(), route: Route::extend(route).unnested(),
tracer,
}; };
let mut state = CounterState::init(&self.0); let mut state = CounterState::init(&self.0);

View File

@ -2,8 +2,7 @@ use comemo::{Track, Tracked, TrackedMut};
use ecow::{eco_format, eco_vec, EcoString, EcoVec}; use ecow::{eco_format, eco_vec, EcoString, EcoVec};
use crate::diag::{bail, At, SourceResult}; use crate::diag::{bail, At, SourceResult};
use crate::engine::{Engine, Route}; use crate::engine::{Engine, Route, Sink, Traced};
use crate::eval::Tracer;
use crate::foundations::{ use crate::foundations::{
cast, elem, func, scope, select_where, ty, Args, Construct, Content, Context, Func, cast, elem, func, scope, select_where, ty, Args, Construct, Content, Context, Func,
LocatableSelector, NativeElement, Packed, Repr, Selector, Show, Str, StyleChain, LocatableSelector, NativeElement, Packed, Repr, Selector, Show, Str, StyleChain,
@ -214,8 +213,9 @@ impl State {
self.sequence_impl( self.sequence_impl(
engine.world, engine.world,
engine.introspector, engine.introspector,
engine.traced,
TrackedMut::reborrow_mut(&mut engine.sink),
engine.route.track(), engine.route.track(),
TrackedMut::reborrow_mut(&mut engine.tracer),
) )
} }
@ -225,14 +225,16 @@ impl State {
&self, &self,
world: Tracked<dyn World + '_>, world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>, introspector: Tracked<Introspector>,
traced: Tracked<Traced>,
sink: TrackedMut<Sink>,
route: Tracked<Route>, route: Tracked<Route>,
tracer: TrackedMut<Tracer>,
) -> SourceResult<EcoVec<Value>> { ) -> SourceResult<EcoVec<Value>> {
let mut engine = Engine { let mut engine = Engine {
world, world,
introspector, introspector,
traced,
sink,
route: Route::extend(route).unnested(), route: Route::extend(route).unnested(),
tracer,
}; };
let mut state = self.init.clone(); let mut state = self.init.clone();
let mut stops = eco_vec![state.clone()]; let mut stops = eco_vec![state.clone()];

View File

@ -11,8 +11,7 @@ use self::shaping::{
END_PUNCT_PAT, END_PUNCT_PAT,
}; };
use crate::diag::{bail, SourceResult}; use crate::diag::{bail, SourceResult};
use crate::engine::{Engine, Route}; use crate::engine::{Engine, Route, Sink, Traced};
use crate::eval::Tracer;
use crate::foundations::{Packed, Resolve, Smart, StyleChain}; use crate::foundations::{Packed, Resolve, Smart, StyleChain};
use crate::introspection::{Introspector, Locator, LocatorLink, Tag, TagElem}; use crate::introspection::{Introspector, Locator, LocatorLink, Tag, TagElem};
use crate::layout::{ use crate::layout::{
@ -45,8 +44,9 @@ pub(crate) fn layout_inline(
children: &StyleVec, children: &StyleVec,
world: Tracked<dyn World + '_>, world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>, introspector: Tracked<Introspector>,
traced: Tracked<Traced>,
sink: TrackedMut<Sink>,
route: Tracked<Route>, route: Tracked<Route>,
tracer: TrackedMut<Tracer>,
locator: Tracked<Locator>, locator: Tracked<Locator>,
styles: StyleChain, styles: StyleChain,
consecutive: bool, consecutive: bool,
@ -58,8 +58,9 @@ pub(crate) fn layout_inline(
let mut engine = Engine { let mut engine = Engine {
world, world,
introspector, introspector,
traced,
sink,
route: Route::extend(route), route: Route::extend(route),
tracer,
}; };
// Collect all text into one string for BiDi analysis. // Collect all text into one string for BiDi analysis.
@ -83,8 +84,9 @@ pub(crate) fn layout_inline(
children, children,
engine.world, engine.world,
engine.introspector, engine.introspector,
engine.traced,
TrackedMut::reborrow_mut(&mut engine.sink),
engine.route.track(), engine.route.track(),
TrackedMut::reborrow_mut(&mut engine.tracer),
locator.track(), locator.track(),
styles, styles,
consecutive, consecutive,

View File

@ -72,8 +72,7 @@ pub(crate) use self::inline::*;
use comemo::{Track, Tracked, TrackedMut}; use comemo::{Track, Tracked, TrackedMut};
use crate::diag::{bail, SourceResult}; use crate::diag::{bail, SourceResult};
use crate::engine::{Engine, Route}; use crate::engine::{Engine, Route, Sink, Traced};
use crate::eval::Tracer;
use crate::foundations::{category, Category, Content, Scope, StyleChain}; use crate::foundations::{category, Category, Content, Scope, StyleChain};
use crate::introspection::{Introspector, Locator, LocatorLink}; use crate::introspection::{Introspector, Locator, LocatorLink};
use crate::model::Document; use crate::model::Document;
@ -137,16 +136,18 @@ impl Content {
content: &Content, content: &Content,
world: Tracked<dyn World + '_>, world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>, introspector: Tracked<Introspector>,
traced: Tracked<Traced>,
sink: TrackedMut<Sink>,
route: Tracked<Route>, route: Tracked<Route>,
tracer: TrackedMut<Tracer>,
styles: StyleChain, styles: StyleChain,
) -> SourceResult<Document> { ) -> SourceResult<Document> {
let mut locator = Locator::root().split(); let mut locator = Locator::root().split();
let mut engine = Engine { let mut engine = Engine {
world, world,
introspector, introspector,
traced,
sink,
route: Route::extend(route).unnested(), route: Route::extend(route).unnested(),
tracer,
}; };
let arenas = Arenas::default(); let arenas = Arenas::default();
let (document, styles) = let (document, styles) =
@ -158,8 +159,9 @@ impl Content {
self, self,
engine.world, engine.world,
engine.introspector, engine.introspector,
engine.traced,
TrackedMut::reborrow_mut(&mut engine.sink),
engine.route.track(), engine.route.track(),
TrackedMut::reborrow_mut(&mut engine.tracer),
styles, styles,
) )
} }
@ -178,8 +180,9 @@ impl Content {
content: &Content, content: &Content,
world: Tracked<dyn World + '_>, world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>, introspector: Tracked<Introspector>,
traced: Tracked<Traced>,
sink: TrackedMut<Sink>,
route: Tracked<Route>, route: Tracked<Route>,
tracer: TrackedMut<Tracer>,
locator: Tracked<Locator>, locator: Tracked<Locator>,
styles: StyleChain, styles: StyleChain,
regions: Regions, regions: Regions,
@ -189,8 +192,9 @@ impl Content {
let mut engine = Engine { let mut engine = Engine {
world, world,
introspector, introspector,
traced,
sink,
route: Route::extend(route), route: Route::extend(route),
tracer,
}; };
if !engine.route.within(Route::MAX_LAYOUT_DEPTH) { if !engine.route.within(Route::MAX_LAYOUT_DEPTH) {
@ -218,8 +222,9 @@ impl Content {
self, self,
engine.world, engine.world,
engine.introspector, engine.introspector,
engine.traced,
TrackedMut::reborrow_mut(&mut engine.sink),
engine.route.track(), engine.route.track(),
TrackedMut::reborrow_mut(&mut engine.tracer),
locator.track(), locator.track(),
styles, styles,
regions, regions,

View File

@ -63,11 +63,10 @@ use comemo::{Track, Tracked, Validate};
use ecow::{EcoString, EcoVec}; use ecow::{EcoString, EcoVec};
use typst_timing::{timed, TimingScope}; use typst_timing::{timed, TimingScope};
use crate::diag::{warning, FileResult, SourceDiagnostic, SourceResult}; use crate::diag::{warning, FileResult, SourceDiagnostic, SourceResult, Warned};
use crate::engine::{Engine, Route}; use crate::engine::{Engine, Route, Sink, Traced};
use crate::eval::Tracer;
use crate::foundations::{ use crate::foundations::{
Array, Bytes, Content, Datetime, Dict, Module, Scope, StyleChain, Styles, Value, Array, Bytes, Datetime, Dict, Module, Scope, StyleChain, Styles, Value,
}; };
use crate::introspection::Introspector; use crate::introspection::Introspector;
use crate::layout::{Alignment, Dir}; use crate::layout::{Alignment, Dir};
@ -78,62 +77,68 @@ use crate::text::{Font, FontBook};
use crate::utils::LazyHash; use crate::utils::LazyHash;
use crate::visualize::Color; use crate::visualize::Color;
/// Compile a source file into a fully layouted document. /// Compile sources into a fully layouted document.
/// ///
/// - Returns `Ok(document)` if there were no fatal errors. /// - Returns `Ok(document)` if there were no fatal errors.
/// - Returns `Err(errors)` if there were fatal errors. /// - Returns `Err(errors)` if there were fatal errors.
/// #[typst_macros::time]
/// Requires a mutable reference to a tracer. Such a tracer can be created with pub fn compile(world: &dyn World) -> Warned<SourceResult<Document>> {
/// `Tracer::new()`. Independently of whether compilation succeeded, calling let mut sink = Sink::new();
/// `tracer.warnings()` after compilation will return all compiler warnings. let output = compile_inner(world.track(), Traced::default().track(), &mut sink)
#[typst_macros::time(name = "compile")] .map_err(deduplicate);
pub fn compile(world: &dyn World, tracer: &mut Tracer) -> SourceResult<Document> { Warned { output, warnings: sink.warnings() }
// Call `track` on the world just once to keep comemo's ID stable. }
let world = world.track();
// Try to evaluate the source file into a module. /// Compiles sources and returns all values and styles observed at the given
let module = crate::eval::eval( /// `span` during compilation.
world, #[typst_macros::time]
Route::default().track(), pub fn trace(world: &dyn World, span: Span) -> EcoVec<(Value, Option<Styles>)> {
tracer.track_mut(), let mut sink = Sink::new();
&world.main(), let traced = Traced::new(span);
) compile_inner(world.track(), traced.track(), &mut sink).ok();
.map_err(deduplicate)?; sink.values()
// Typeset the module's content, relayouting until convergence.
typeset(world, tracer, &module.content()).map_err(deduplicate)
} }
/// Relayout until introspection converges. /// Relayout until introspection converges.
fn typeset( fn compile_inner(
world: Tracked<dyn World + '_>, world: Tracked<dyn World + '_>,
tracer: &mut Tracer, traced: Tracked<Traced>,
content: &Content, sink: &mut Sink,
) -> SourceResult<Document> { ) -> SourceResult<Document> {
// The name of the iterations for timing scopes.
const ITER_NAMES: &[&str] =
&["typeset (1)", "typeset (2)", "typeset (3)", "typeset (4)", "typeset (5)"];
let library = world.library(); let library = world.library();
let styles = StyleChain::new(&library.styles); let styles = StyleChain::new(&library.styles);
// First evaluate the main source file into a module.
let content = crate::eval::eval(
world,
traced,
sink.track_mut(),
Route::default().track(),
&world.main(),
)?
.content();
let mut iter = 0; let mut iter = 0;
let mut document = Document::default(); let mut document = Document::default();
// Relayout until all introspections stabilize. // Relayout until all introspections stabilize.
// If that doesn't happen within five attempts, we give up. // If that doesn't happen within five attempts, we give up.
loop { loop {
// The name of the iterations for timing scopes.
const ITER_NAMES: &[&str] =
&["layout (1)", "layout (2)", "layout (3)", "layout (4)", "layout (5)"];
let _scope = TimingScope::new(ITER_NAMES[iter], None); let _scope = TimingScope::new(ITER_NAMES[iter], None);
// Clear delayed errors. // Clear delayed errors.
tracer.delayed(); sink.delayed();
let constraint = <Introspector as Validate>::Constraint::new(); let constraint = <Introspector as Validate>::Constraint::new();
let mut engine = Engine { let mut engine = Engine {
world, world,
route: Route::default(),
tracer: tracer.track_mut(),
introspector: document.introspector.track_with(&constraint), introspector: document.introspector.track_with(&constraint),
traced,
sink: sink.track_mut(),
route: Route::default(),
}; };
// Layout! // Layout!
@ -146,7 +151,7 @@ fn typeset(
} }
if iter >= 5 { if iter >= 5 {
tracer.warn(warning!( sink.warn(warning!(
Span::detached(), "layout did not converge within 5 attempts"; Span::detached(), "layout did not converge within 5 attempts";
hint: "check if any states or queries are updating themselves" hint: "check if any states or queries are updating themselves"
)); ));
@ -155,7 +160,7 @@ fn typeset(
} }
// Promote delayed errors. // Promote delayed errors.
let delayed = tracer.delayed(); let delayed = sink.delayed();
if !delayed.is_empty() { if !delayed.is_empty() {
return Err(delayed); return Err(delayed);
} }

View File

@ -62,7 +62,7 @@ pub fn process(
// //
// This way, we can ignore errors that only occur in earlier // This way, we can ignore errors that only occur in earlier
// iterations and also show more useful errors at once. // iterations and also show more useful errors at once.
engine.delayed(|engine| show(engine, target, step, styles.chain(&map))) engine.delay(|engine| show(engine, target, step, styles.chain(&map)))
} }
None => target, None => target,
}; };

View File

@ -132,7 +132,7 @@ pub struct TextElem {
let book = engine.world.book(); let book = engine.world.book();
for family in &font_list.v { for family in &font_list.v {
if !book.contains_family(family.as_str()) { if !book.contains_family(family.as_str()) {
engine.tracer.warn(warning!( engine.sink.warn(warning!(
font_list.span, font_list.span,
"unknown font family: {}", "unknown font family: {}",
family.as_str(), family.as_str(),

View File

@ -7,7 +7,6 @@ use pulldown_cmark as md;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use typed_arena::Arena; use typed_arena::Arena;
use typst::diag::{FileResult, StrResult}; use typst::diag::{FileResult, StrResult};
use typst::eval::Tracer;
use typst::foundations::{Bytes, Datetime}; use typst::foundations::{Bytes, Datetime};
use typst::layout::{Abs, Point, Size}; use typst::layout::{Abs, Point, Size};
use typst::syntax::{FileId, Source, VirtualPath}; use typst::syntax::{FileId, Source, VirtualPath};
@ -411,8 +410,7 @@ fn code_block(resolver: &dyn Resolver, lang: &str, text: &str) -> Html {
let source = Source::new(id, compile); let source = Source::new(id, compile);
let world = DocWorld(source); let world = DocWorld(source);
let mut tracer = Tracer::new(); let mut document = match typst::compile(&world).output {
let mut document = match typst::compile(&world, &mut tracer) {
Ok(doc) => doc, Ok(doc) => doc,
Err(err) => { Err(err) => {
let msg = &err[0].message; let msg = &err[0].message;

View File

@ -2,7 +2,6 @@
use libfuzzer_sys::fuzz_target; use libfuzzer_sys::fuzz_target;
use typst::diag::{FileError, FileResult}; use typst::diag::{FileError, FileResult};
use typst::eval::Tracer;
use typst::foundations::{Bytes, Datetime}; use typst::foundations::{Bytes, Datetime};
use typst::syntax::{FileId, Source}; use typst::syntax::{FileId, Source};
use typst::text::{Font, FontBook}; use typst::text::{Font, FontBook};
@ -63,8 +62,7 @@ impl World for FuzzWorld {
fuzz_target!(|text: &str| { fuzz_target!(|text: &str| {
let world = FuzzWorld::new(text); let world = FuzzWorld::new(text);
let mut tracer = Tracer::new(); if let Ok(document) = typst::compile(&world).output {
if let Ok(document) = typst::compile(&world, &mut tracer) {
if let Some(page) = document.pages.first() { if let Some(page) = document.pages.first() {
std::hint::black_box(typst_render::render(&page.frame, 1.0, Color::WHITE)); std::hint::black_box(typst_render::render(&page.frame, 1.0, Color::WHITE));
} }

View File

@ -4,8 +4,7 @@ use std::path::Path;
use ecow::eco_vec; use ecow::eco_vec;
use tiny_skia as sk; use tiny_skia as sk;
use typst::diag::SourceDiagnostic; use typst::diag::{SourceDiagnostic, Warned};
use typst::eval::Tracer;
use typst::foundations::Smart; use typst::foundations::Smart;
use typst::layout::{Abs, Frame, FrameItem, Page, Transform}; use typst::layout::{Abs, Frame, FrameItem, Page, Transform};
use typst::model::Document; use typst::model::Document;
@ -80,13 +79,12 @@ impl<'a> Runner<'a> {
log!(into: self.result.infos, "tree: {:#?}", self.test.source.root()); log!(into: self.result.infos, "tree: {:#?}", self.test.source.root());
} }
let mut tracer = Tracer::new(); let Warned { output, warnings } = typst::compile(&self.world);
let (doc, errors) = match typst::compile(&self.world, &mut tracer) { let (doc, errors) = match output {
Ok(doc) => (Some(doc), eco_vec![]), Ok(doc) => (Some(doc), eco_vec![]),
Err(errors) => (None, errors), Err(errors) => (None, errors),
}; };
let warnings = tracer.warnings();
if doc.is_none() && errors.is_empty() { if doc.is_none() && errors.is_empty() {
log!(self, "no document, but also no errors"); log!(self, "no document, but also no errors");
} }