From a68a241570eca6d46f916e3ee103664a4eb79333 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 10 Jun 2024 11:00:54 +0200 Subject: [PATCH] Remove `Tracer` (#4365) --- crates/typst-cli/src/compile.rs | 9 +- crates/typst-cli/src/query.rs | 10 +- crates/typst-ide/src/analyze.rs | 17 ++-- crates/typst-ide/src/complete.rs | 3 +- crates/typst-ide/src/jump.rs | 5 +- crates/typst-ide/src/tooltip.rs | 8 +- crates/typst/src/diag.rs | 9 ++ crates/typst/src/engine.rs | 117 ++++++++++++++++++++-- crates/typst/src/eval/call.rs | 10 +- crates/typst/src/eval/import.rs | 10 +- crates/typst/src/eval/markup.rs | 4 +- crates/typst/src/eval/mod.rs | 18 ++-- crates/typst/src/eval/tracer.rs | 82 --------------- crates/typst/src/eval/vm.rs | 4 +- crates/typst/src/foundations/func.rs | 3 +- crates/typst/src/introspection/counter.rs | 12 ++- crates/typst/src/introspection/state.rs | 12 ++- crates/typst/src/layout/inline/mod.rs | 12 ++- crates/typst/src/layout/mod.rs | 21 ++-- crates/typst/src/lib.rs | 77 +++++++------- crates/typst/src/realize/process.rs | 2 +- crates/typst/src/text/mod.rs | 2 +- docs/src/html.rs | 4 +- tests/fuzz/src/compile.rs | 4 +- tests/src/run.rs | 8 +- 25 files changed, 251 insertions(+), 212 deletions(-) delete mode 100644 crates/typst/src/eval/tracer.rs diff --git a/crates/typst-cli/src/compile.rs b/crates/typst-cli/src/compile.rs index 584ccc858..ae712a85e 100644 --- a/crates/typst-cli/src/compile.rs +++ b/crates/typst-cli/src/compile.rs @@ -8,8 +8,7 @@ use codespan_reporting::term; use ecow::{eco_format, eco_vec, EcoString, EcoVec}; use parking_lot::RwLock; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; -use typst::diag::{bail, FileError, Severity, SourceDiagnostic, StrResult}; -use typst::eval::Tracer; +use typst::diag::{bail, FileError, Severity, SourceDiagnostic, StrResult, Warned}; use typst::foundations::{Datetime, Smart}; use typst::layout::{Frame, PageRanges}; use typst::model::Document; @@ -112,11 +111,9 @@ pub fn compile_once( return Ok(()); } - let mut tracer = Tracer::new(); - let result = typst::compile(world, &mut tracer); - let warnings = tracer.warnings(); + let Warned { output, warnings } = typst::compile(world); - match result { + match output { // Export the PDF / PNG. Ok(document) => { export(world, &document, command, watching)?; diff --git a/crates/typst-cli/src/query.rs b/crates/typst-cli/src/query.rs index 2e8bf7f82..a80127779 100644 --- a/crates/typst-cli/src/query.rs +++ b/crates/typst-cli/src/query.rs @@ -1,8 +1,8 @@ use comemo::Track; use ecow::{eco_format, EcoString}; use serde::Serialize; -use typst::diag::{bail, HintedStrResult, StrResult}; -use typst::eval::{eval_string, EvalMode, Tracer}; +use typst::diag::{bail, HintedStrResult, StrResult, Warned}; +use typst::eval::{eval_string, EvalMode}; use typst::foundations::{Content, IntoValue, LocatableSelector, Scope}; use typst::model::Document; use typst::syntax::Span; @@ -21,11 +21,9 @@ pub fn query(command: &QueryCommand) -> HintedStrResult<()> { world.reset(); world.source(world.main()).map_err(|err| err.to_string())?; - let mut tracer = Tracer::new(); - let result = typst::compile(&world, &mut tracer); - let warnings = tracer.warnings(); + let Warned { output, warnings } = typst::compile(&world); - match result { + match output { // Retrieve and print query results. Ok(document) => { let data = retrieve(&world, command, &document)?; diff --git a/crates/typst-ide/src/analyze.rs b/crates/typst-ide/src/analyze.rs index d27ce1766..b43d87cde 100644 --- a/crates/typst-ide/src/analyze.rs +++ b/crates/typst-ide/src/analyze.rs @@ -1,7 +1,7 @@ use comemo::Track; use ecow::{eco_vec, EcoString, EcoVec}; -use typst::engine::{Engine, Route}; -use typst::eval::{Tracer, Vm}; +use typst::engine::{Engine, Route, Sink, Traced}; +use typst::eval::Vm; use typst::foundations::{Context, Label, Scopes, Styles, Value}; use typst::introspection::Introspector; use typst::model::{BibliographyElem, Document}; @@ -38,10 +38,7 @@ pub fn analyze_expr( } } - let mut tracer = Tracer::new(); - tracer.inspect(node.span()); - typst::compile(world, &mut tracer).ok(); - return tracer.values(); + return typst::trace(world, node.span()); } }; @@ -59,12 +56,14 @@ pub fn analyze_import(world: &dyn World, source: &LinkedNode) -> Option { } let introspector = Introspector::default(); - let mut tracer = Tracer::new(); + let traced = Traced::default(); + let mut sink = Sink::new(); let engine = Engine { world: world.track(), - route: Route::default(), introspector: introspector.track(), - tracer: tracer.track_mut(), + traced: traced.track(), + sink: sink.track_mut(), + route: Route::default(), }; let context = Context::none(); diff --git a/crates/typst-ide/src/complete.rs b/crates/typst-ide/src/complete.rs index 2c4680d1c..608b14730 100644 --- a/crates/typst-ide/src/complete.rs +++ b/crates/typst-ide/src/complete.rs @@ -1406,7 +1406,6 @@ impl<'a> CompletionContext<'a> { #[cfg(test)] mod tests { - use typst::eval::Tracer; use super::autocomplete; use crate::tests::TestWorld; @@ -1414,7 +1413,7 @@ mod tests { #[track_caller] fn test(text: &str, cursor: usize, contains: &[&str], excludes: &[&str]) { let world = TestWorld::new(text); - let doc = typst::compile(&world, &mut Tracer::new()).ok(); + let doc = typst::compile(&world).output.ok(); let (_, completions) = autocomplete(&world, doc.as_ref(), &world.main, cursor, true) .unwrap_or_default(); diff --git a/crates/typst-ide/src/jump.rs b/crates/typst-ide/src/jump.rs index 3bcaa7a38..f632d9332 100644 --- a/crates/typst-ide/src/jump.rs +++ b/crates/typst-ide/src/jump.rs @@ -170,7 +170,6 @@ fn is_in_rect(pos: Point, size: Size, click: Point) -> bool { mod tests { use std::num::NonZeroUsize; - use typst::eval::Tracer; use typst::layout::{Abs, Point, Position}; use super::{jump_from_click, jump_from_cursor, Jump}; @@ -200,14 +199,14 @@ mod tests { #[track_caller] fn test_click(text: &str, click: Point, expected: Option) { 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); } #[track_caller] fn test_cursor(text: &str, cursor: usize, expected: Option) { 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); assert_eq!(pos.is_some(), expected.is_some()); if let (Some(pos), Some(expected)) = (pos, expected) { diff --git a/crates/typst-ide/src/tooltip.rs b/crates/typst-ide/src/tooltip.rs index 4864a2660..3bf8bb14a 100644 --- a/crates/typst-ide/src/tooltip.rs +++ b/crates/typst-ide/src/tooltip.rs @@ -2,7 +2,8 @@ use std::fmt::Write; use ecow::{eco_format, EcoString}; 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::layout::Length; use typst::model::Document; @@ -79,7 +80,7 @@ fn expr_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option { let mut last = None; let mut pieces: Vec = vec![]; 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 *prev == value { *count += 1; @@ -253,7 +254,6 @@ fn font_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option { #[cfg(test)] mod tests { - use typst::eval::Tracer; use typst::syntax::Side; use super::{tooltip, Tooltip}; @@ -270,7 +270,7 @@ mod tests { #[track_caller] fn test(text: &str, cursor: usize, side: Side, expected: Option) { 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); } diff --git a/crates/typst/src/diag.rs b/crates/typst/src/diag.rs index db8a93fd8..6dd88556d 100644 --- a/crates/typst/src/diag.rs +++ b/crates/typst/src/diag.rs @@ -138,6 +138,15 @@ pub use { /// A result that can carry multiple source errors. pub type SourceResult = Result>; +/// An output alongside warnings generated while producing it. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct Warned { + /// The produced output. + pub output: T, + /// Warnings generated while producing the output. + pub warnings: EcoVec, +} + /// An error or warning in a source file. /// /// The contained spans will only be detached if any of the input source files diff --git a/crates/typst/src/engine.rs b/crates/typst/src/engine.rs index a57a8f1ca..ac61cc33b 100644 --- a/crates/typst/src/engine.rs +++ b/crates/typst/src/engine.rs @@ -1,13 +1,15 @@ //! Definition of the central compilation context. +use std::collections::HashSet; use std::sync::atomic::{AtomicUsize, Ordering}; use comemo::{Track, Tracked, TrackedMut, Validate}; +use ecow::EcoVec; -use crate::diag::SourceResult; -use crate::eval::Tracer; +use crate::diag::{SourceDiagnostic, SourceResult}; +use crate::foundations::{Styles, Value}; use crate::introspection::Introspector; -use crate::syntax::FileId; +use crate::syntax::{FileId, Span}; use crate::World; /// Holds all data needed during compilation. @@ -16,18 +18,20 @@ pub struct Engine<'a> { pub world: Tracked<'a, dyn World + 'a>, /// Provides access to information about the document. 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 /// cyclic imports and excessive nesting. pub route: Route<'a>, - /// The tracer for inspection of the values an expression produces. - pub tracer: TrackedMut<'a, Tracer>, } impl Engine<'_> { /// Performs a fallible operation that does not immediately terminate further /// 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. - pub fn delayed(&mut self, f: F) -> T + pub fn delay(&mut self, f: F) -> T where F: FnOnce(&mut Self) -> SourceResult, T: Default, @@ -35,13 +39,112 @@ impl Engine<'_> { match f(self) { Ok(value) => value, Err(errors) => { - self.tracer.delay(errors); + self.sink.delay(errors); T::default() } } } } +/// May hold a span that is currently under inspection. +#[derive(Default)] +pub struct Traced(Option); + +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 { + 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, + /// Warnings emitted during iteration. + warnings: EcoVec, + /// Hashes of all warning's spans and messages for warning deduplication. + warnings_set: HashSet, + /// A sequence of traced values for a span. + values: EcoVec<(Value, Option)>, +} + +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 { + std::mem::take(&mut self.delayed) + } + + /// Get the stored warnings. + pub fn warnings(self) -> EcoVec { + self.warnings + } + + /// Get the values for the traced span. + pub fn values(self) -> EcoVec<(Value, Option)> { + self.values + } +} + +#[comemo::track] +impl Sink { + /// Push delayed errors. + pub fn delay(&mut self, errors: EcoVec) { + 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) { + if self.values.len() < Self::MAX_VALUES { + self.values.push((value, styles)); + } + } +} + /// The route the engine took during compilation. This is used to detect /// cyclic imports and excessive nesting. pub struct Route<'a> { diff --git a/crates/typst/src/eval/call.rs b/crates/typst/src/eval/call.rs index d4b81f029..278c3afb6 100644 --- a/crates/typst/src/eval/call.rs +++ b/crates/typst/src/eval/call.rs @@ -2,8 +2,8 @@ use comemo::{Tracked, TrackedMut}; use ecow::{eco_format, EcoVec}; use crate::diag::{bail, error, At, HintedStrResult, SourceResult, Trace, Tracepoint}; -use crate::engine::Engine; -use crate::eval::{Access, Eval, FlowEvent, Route, Tracer, Vm}; +use crate::engine::{Engine, Sink, Traced}; +use crate::eval::{Access, Eval, FlowEvent, Route, Vm}; use crate::foundations::{ call_method_mut, is_mutating_method, Arg, Args, Bytes, Capturer, Closure, Content, Context, Func, IntoValue, NativeElement, Scope, Scopes, Value, @@ -275,8 +275,9 @@ pub(crate) fn call_closure( closure: &LazyHash, world: Tracked, introspector: Tracked, + traced: Tracked, + sink: TrackedMut, route: Tracked, - tracer: TrackedMut, context: Tracked, mut args: Args, ) -> SourceResult { @@ -294,8 +295,9 @@ pub(crate) fn call_closure( let engine = Engine { world, introspector, + traced, + sink, route: Route::extend(route), - tracer, }; // Prepare VM. diff --git a/crates/typst/src/eval/import.rs b/crates/typst/src/eval/import.rs index fbd55b7c5..da07e6664 100644 --- a/crates/typst/src/eval/import.rs +++ b/crates/typst/src/eval/import.rs @@ -35,7 +35,7 @@ impl Eval for ast::ModuleImport<'_> { if let ast::Expr::Ident(ident) = self.source() { if ident.as_str() == new_name.as_str() { // Warn on `import x as x` - vm.engine.tracer.warn(warning!( + vm.engine.sink.warn(warning!( new_name.span(), "unnecessary import rename to same name", )); @@ -110,7 +110,7 @@ impl Eval for ast::ModuleImport<'_> { if renamed_item.original_name().as_str() == renamed_item.new_name().as_str() { - vm.engine.tracer.warn(warning!( + vm.engine.sink.warn(warning!( renamed_item.new_name().span(), "unnecessary import rename to same name", )); @@ -185,8 +185,9 @@ fn import_package(vm: &mut Vm, spec: PackageSpec, span: Span) -> SourceResult SourceResult { let point = || Tracepoint::Import; eval( world, + vm.engine.traced, + TrackedMut::reborrow_mut(&mut vm.engine.sink), vm.engine.route.track(), - TrackedMut::reborrow_mut(&mut vm.engine.tracer), &source, ) .trace(world, point, span) diff --git a/crates/typst/src/eval/markup.rs b/crates/typst/src/eval/markup.rs index d43e44956..e38f137df 100644 --- a/crates/typst/src/eval/markup.rs +++ b/crates/typst/src/eval/markup.rs @@ -134,7 +134,7 @@ impl Eval for ast::Strong<'_> { let body = self.body(); if body.exprs().next().is_none() { vm.engine - .tracer + .sink .warn(warning!( self.span(), "no text within stars"; hint: "using multiple consecutive stars (e.g. **) has no additional effect", @@ -152,7 +152,7 @@ impl Eval for ast::Emph<'_> { let body = self.body(); if body.exprs().next().is_none() { vm.engine - .tracer + .sink .warn(warning!( self.span(), "no text within underscores"; hint: "using multiple consecutive underscores (e.g. __) has no additional effect" diff --git a/crates/typst/src/eval/mod.rs b/crates/typst/src/eval/mod.rs index 2e5eeafd0..e4221df77 100644 --- a/crates/typst/src/eval/mod.rs +++ b/crates/typst/src/eval/mod.rs @@ -11,12 +11,10 @@ mod import; mod markup; mod math; mod rules; -mod tracer; mod vm; pub use self::call::*; pub use self::import::*; -pub use self::tracer::*; pub use self::vm::*; pub(crate) use self::access::*; @@ -26,7 +24,7 @@ pub(crate) use self::flow::*; use comemo::{Track, Tracked, TrackedMut}; 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::introspection::Introspector; use crate::math::EquationElem; @@ -38,8 +36,9 @@ use crate::World; #[typst_macros::time(name = "eval", span = source.root().span())] pub fn eval( world: Tracked, + traced: Tracked, + sink: TrackedMut, route: Tracked, - tracer: TrackedMut, source: &Source, ) -> SourceResult { // Prevent cyclic evaluation. @@ -52,9 +51,10 @@ pub fn eval( let introspector = Introspector::default(); let engine = Engine { world, - route: Route::extend(route).with_id(id), introspector: introspector.track(), - tracer, + traced, + sink, + route: Route::extend(route).with_id(id), }; // Prepare VM. @@ -115,13 +115,15 @@ pub fn eval_string( } // Prepare the engine. - let mut tracer = Tracer::new(); + let mut sink = Sink::new(); let introspector = Introspector::default(); + let traced = Traced::default(); let engine = Engine { world, introspector: introspector.track(), + traced: traced.track(), + sink: sink.track_mut(), route: Route::default(), - tracer: tracer.track_mut(), }; // Prepare VM. diff --git a/crates/typst/src/eval/tracer.rs b/crates/typst/src/eval/tracer.rs deleted file mode 100644 index 30c640f39..000000000 --- a/crates/typst/src/eval/tracer.rs +++ /dev/null @@ -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, - warnings: EcoVec, - warnings_set: HashSet, - delayed: EcoVec, - values: EcoVec<(Value, Option)>, -} - -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 { - std::mem::take(&mut self.delayed) - } - - /// Get the stored warnings. - pub fn warnings(self) -> EcoVec { - 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)> { - self.values - } -} - -#[comemo::track] -impl Tracer { - /// Push delayed errors. - pub fn delay(&mut self, errors: EcoVec) { - 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 { - 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) { - if self.values.len() < Self::MAX_VALUES { - self.values.push((value, styles)); - } - } -} diff --git a/crates/typst/src/eval/vm.rs b/crates/typst/src/eval/vm.rs index be43b5bae..27960eb6d 100644 --- a/crates/typst/src/eval/vm.rs +++ b/crates/typst/src/eval/vm.rs @@ -32,7 +32,7 @@ impl<'a> Vm<'a> { scopes: Scopes<'a>, target: Span, ) -> 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 } } @@ -54,7 +54,7 @@ impl<'a> Vm<'a> { #[cold] pub fn trace(&mut self, value: Value) { self.engine - .tracer + .sink .value(value.clone(), self.context.styles().ok().map(|s| s.to_map())); } } diff --git a/crates/typst/src/foundations/func.rs b/crates/typst/src/foundations/func.rs index f5b4b3ca0..dc5ea8dfc 100644 --- a/crates/typst/src/foundations/func.rs +++ b/crates/typst/src/foundations/func.rs @@ -297,8 +297,9 @@ impl Func { closure, engine.world, engine.introspector, + engine.traced, + TrackedMut::reborrow_mut(&mut engine.sink), engine.route.track(), - TrackedMut::reborrow_mut(&mut engine.tracer), context, args, ), diff --git a/crates/typst/src/introspection/counter.rs b/crates/typst/src/introspection/counter.rs index df16026fe..13ea4d142 100644 --- a/crates/typst/src/introspection/counter.rs +++ b/crates/typst/src/introspection/counter.rs @@ -6,8 +6,7 @@ use ecow::{eco_format, eco_vec, EcoString, EcoVec}; use smallvec::{smallvec, SmallVec}; use crate::diag::{bail, At, HintedStrResult, SourceResult}; -use crate::engine::{Engine, Route}; -use crate::eval::Tracer; +use crate::engine::{Engine, Route, Sink, Traced}; use crate::foundations::{ cast, elem, func, scope, select_where, ty, Args, Array, Construct, Content, Context, Element, Func, IntoValue, Label, LocatableSelector, NativeElement, Packed, Repr, @@ -281,8 +280,9 @@ impl Counter { self.sequence_impl( engine.world, engine.introspector, + engine.traced, + TrackedMut::reborrow_mut(&mut engine.sink), engine.route.track(), - TrackedMut::reborrow_mut(&mut engine.tracer), ) } @@ -292,14 +292,16 @@ impl Counter { &self, world: Tracked, introspector: Tracked, + traced: Tracked, + sink: TrackedMut, route: Tracked, - tracer: TrackedMut, ) -> SourceResult> { let mut engine = Engine { world, introspector, + traced, + sink, route: Route::extend(route).unnested(), - tracer, }; let mut state = CounterState::init(&self.0); diff --git a/crates/typst/src/introspection/state.rs b/crates/typst/src/introspection/state.rs index bf97c8746..be114c86f 100644 --- a/crates/typst/src/introspection/state.rs +++ b/crates/typst/src/introspection/state.rs @@ -2,8 +2,7 @@ use comemo::{Track, Tracked, TrackedMut}; use ecow::{eco_format, eco_vec, EcoString, EcoVec}; use crate::diag::{bail, At, SourceResult}; -use crate::engine::{Engine, Route}; -use crate::eval::Tracer; +use crate::engine::{Engine, Route, Sink, Traced}; use crate::foundations::{ cast, elem, func, scope, select_where, ty, Args, Construct, Content, Context, Func, LocatableSelector, NativeElement, Packed, Repr, Selector, Show, Str, StyleChain, @@ -214,8 +213,9 @@ impl State { self.sequence_impl( engine.world, engine.introspector, + engine.traced, + TrackedMut::reborrow_mut(&mut engine.sink), engine.route.track(), - TrackedMut::reborrow_mut(&mut engine.tracer), ) } @@ -225,14 +225,16 @@ impl State { &self, world: Tracked, introspector: Tracked, + traced: Tracked, + sink: TrackedMut, route: Tracked, - tracer: TrackedMut, ) -> SourceResult> { let mut engine = Engine { world, introspector, + traced, + sink, route: Route::extend(route).unnested(), - tracer, }; let mut state = self.init.clone(); let mut stops = eco_vec![state.clone()]; diff --git a/crates/typst/src/layout/inline/mod.rs b/crates/typst/src/layout/inline/mod.rs index e6e3d88ce..49bad3f70 100644 --- a/crates/typst/src/layout/inline/mod.rs +++ b/crates/typst/src/layout/inline/mod.rs @@ -11,8 +11,7 @@ use self::shaping::{ END_PUNCT_PAT, }; use crate::diag::{bail, SourceResult}; -use crate::engine::{Engine, Route}; -use crate::eval::Tracer; +use crate::engine::{Engine, Route, Sink, Traced}; use crate::foundations::{Packed, Resolve, Smart, StyleChain}; use crate::introspection::{Introspector, Locator, LocatorLink, Tag, TagElem}; use crate::layout::{ @@ -45,8 +44,9 @@ pub(crate) fn layout_inline( children: &StyleVec, world: Tracked, introspector: Tracked, + traced: Tracked, + sink: TrackedMut, route: Tracked, - tracer: TrackedMut, locator: Tracked, styles: StyleChain, consecutive: bool, @@ -58,8 +58,9 @@ pub(crate) fn layout_inline( let mut engine = Engine { world, introspector, + traced, + sink, route: Route::extend(route), - tracer, }; // Collect all text into one string for BiDi analysis. @@ -83,8 +84,9 @@ pub(crate) fn layout_inline( children, engine.world, engine.introspector, + engine.traced, + TrackedMut::reborrow_mut(&mut engine.sink), engine.route.track(), - TrackedMut::reborrow_mut(&mut engine.tracer), locator.track(), styles, consecutive, diff --git a/crates/typst/src/layout/mod.rs b/crates/typst/src/layout/mod.rs index 843b43f54..85cdbae7a 100644 --- a/crates/typst/src/layout/mod.rs +++ b/crates/typst/src/layout/mod.rs @@ -72,8 +72,7 @@ pub(crate) use self::inline::*; use comemo::{Track, Tracked, TrackedMut}; use crate::diag::{bail, SourceResult}; -use crate::engine::{Engine, Route}; -use crate::eval::Tracer; +use crate::engine::{Engine, Route, Sink, Traced}; use crate::foundations::{category, Category, Content, Scope, StyleChain}; use crate::introspection::{Introspector, Locator, LocatorLink}; use crate::model::Document; @@ -137,16 +136,18 @@ impl Content { content: &Content, world: Tracked, introspector: Tracked, + traced: Tracked, + sink: TrackedMut, route: Tracked, - tracer: TrackedMut, styles: StyleChain, ) -> SourceResult { let mut locator = Locator::root().split(); let mut engine = Engine { world, introspector, + traced, + sink, route: Route::extend(route).unnested(), - tracer, }; let arenas = Arenas::default(); let (document, styles) = @@ -158,8 +159,9 @@ impl Content { self, engine.world, engine.introspector, + engine.traced, + TrackedMut::reborrow_mut(&mut engine.sink), engine.route.track(), - TrackedMut::reborrow_mut(&mut engine.tracer), styles, ) } @@ -178,8 +180,9 @@ impl Content { content: &Content, world: Tracked, introspector: Tracked, + traced: Tracked, + sink: TrackedMut, route: Tracked, - tracer: TrackedMut, locator: Tracked, styles: StyleChain, regions: Regions, @@ -189,8 +192,9 @@ impl Content { let mut engine = Engine { world, introspector, + traced, + sink, route: Route::extend(route), - tracer, }; if !engine.route.within(Route::MAX_LAYOUT_DEPTH) { @@ -218,8 +222,9 @@ impl Content { self, engine.world, engine.introspector, + engine.traced, + TrackedMut::reborrow_mut(&mut engine.sink), engine.route.track(), - TrackedMut::reborrow_mut(&mut engine.tracer), locator.track(), styles, regions, diff --git a/crates/typst/src/lib.rs b/crates/typst/src/lib.rs index 6d445edd6..50575d129 100644 --- a/crates/typst/src/lib.rs +++ b/crates/typst/src/lib.rs @@ -63,11 +63,10 @@ use comemo::{Track, Tracked, Validate}; use ecow::{EcoString, EcoVec}; use typst_timing::{timed, TimingScope}; -use crate::diag::{warning, FileResult, SourceDiagnostic, SourceResult}; -use crate::engine::{Engine, Route}; -use crate::eval::Tracer; +use crate::diag::{warning, FileResult, SourceDiagnostic, SourceResult, Warned}; +use crate::engine::{Engine, Route, Sink, Traced}; 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::layout::{Alignment, Dir}; @@ -78,62 +77,68 @@ use crate::text::{Font, FontBook}; use crate::utils::LazyHash; 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 `Err(errors)` if there were fatal errors. -/// -/// Requires a mutable reference to a tracer. Such a tracer can be created with -/// `Tracer::new()`. Independently of whether compilation succeeded, calling -/// `tracer.warnings()` after compilation will return all compiler warnings. -#[typst_macros::time(name = "compile")] -pub fn compile(world: &dyn World, tracer: &mut Tracer) -> SourceResult { - // Call `track` on the world just once to keep comemo's ID stable. - let world = world.track(); +#[typst_macros::time] +pub fn compile(world: &dyn World) -> Warned> { + let mut sink = Sink::new(); + let output = compile_inner(world.track(), Traced::default().track(), &mut sink) + .map_err(deduplicate); + Warned { output, warnings: sink.warnings() } +} - // Try to evaluate the source file into a module. - let module = crate::eval::eval( - world, - Route::default().track(), - tracer.track_mut(), - &world.main(), - ) - .map_err(deduplicate)?; - - // Typeset the module's content, relayouting until convergence. - typeset(world, tracer, &module.content()).map_err(deduplicate) +/// Compiles sources and returns all values and styles observed at the given +/// `span` during compilation. +#[typst_macros::time] +pub fn trace(world: &dyn World, span: Span) -> EcoVec<(Value, Option)> { + let mut sink = Sink::new(); + let traced = Traced::new(span); + compile_inner(world.track(), traced.track(), &mut sink).ok(); + sink.values() } /// Relayout until introspection converges. -fn typeset( +fn compile_inner( world: Tracked, - tracer: &mut Tracer, - content: &Content, + traced: Tracked, + sink: &mut Sink, ) -> SourceResult { - // 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 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 document = Document::default(); // Relayout until all introspections stabilize. // If that doesn't happen within five attempts, we give up. 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); // Clear delayed errors. - tracer.delayed(); + sink.delayed(); let constraint = ::Constraint::new(); let mut engine = Engine { world, - route: Route::default(), - tracer: tracer.track_mut(), introspector: document.introspector.track_with(&constraint), + traced, + sink: sink.track_mut(), + route: Route::default(), }; // Layout! @@ -146,7 +151,7 @@ fn typeset( } if iter >= 5 { - tracer.warn(warning!( + sink.warn(warning!( Span::detached(), "layout did not converge within 5 attempts"; hint: "check if any states or queries are updating themselves" )); @@ -155,7 +160,7 @@ fn typeset( } // Promote delayed errors. - let delayed = tracer.delayed(); + let delayed = sink.delayed(); if !delayed.is_empty() { return Err(delayed); } diff --git a/crates/typst/src/realize/process.rs b/crates/typst/src/realize/process.rs index a0ba8fdb3..150f07ab7 100644 --- a/crates/typst/src/realize/process.rs +++ b/crates/typst/src/realize/process.rs @@ -62,7 +62,7 @@ pub fn process( // // This way, we can ignore errors that only occur in earlier // 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, }; diff --git a/crates/typst/src/text/mod.rs b/crates/typst/src/text/mod.rs index 36b3de261..7648f08fa 100644 --- a/crates/typst/src/text/mod.rs +++ b/crates/typst/src/text/mod.rs @@ -132,7 +132,7 @@ pub struct TextElem { let book = engine.world.book(); for family in &font_list.v { if !book.contains_family(family.as_str()) { - engine.tracer.warn(warning!( + engine.sink.warn(warning!( font_list.span, "unknown font family: {}", family.as_str(), diff --git a/docs/src/html.rs b/docs/src/html.rs index b5c83895f..ab140a902 100644 --- a/docs/src/html.rs +++ b/docs/src/html.rs @@ -7,7 +7,6 @@ use pulldown_cmark as md; use serde::{Deserialize, Serialize}; use typed_arena::Arena; use typst::diag::{FileResult, StrResult}; -use typst::eval::Tracer; use typst::foundations::{Bytes, Datetime}; use typst::layout::{Abs, Point, Size}; 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 world = DocWorld(source); - let mut tracer = Tracer::new(); - let mut document = match typst::compile(&world, &mut tracer) { + let mut document = match typst::compile(&world).output { Ok(doc) => doc, Err(err) => { let msg = &err[0].message; diff --git a/tests/fuzz/src/compile.rs b/tests/fuzz/src/compile.rs index 4dbea4100..98c300ce5 100644 --- a/tests/fuzz/src/compile.rs +++ b/tests/fuzz/src/compile.rs @@ -2,7 +2,6 @@ use libfuzzer_sys::fuzz_target; use typst::diag::{FileError, FileResult}; -use typst::eval::Tracer; use typst::foundations::{Bytes, Datetime}; use typst::syntax::{FileId, Source}; use typst::text::{Font, FontBook}; @@ -63,8 +62,7 @@ impl World for FuzzWorld { fuzz_target!(|text: &str| { let world = FuzzWorld::new(text); - let mut tracer = Tracer::new(); - if let Ok(document) = typst::compile(&world, &mut tracer) { + if let Ok(document) = typst::compile(&world).output { if let Some(page) = document.pages.first() { std::hint::black_box(typst_render::render(&page.frame, 1.0, Color::WHITE)); } diff --git a/tests/src/run.rs b/tests/src/run.rs index a59ce5369..3db03ba43 100644 --- a/tests/src/run.rs +++ b/tests/src/run.rs @@ -4,8 +4,7 @@ use std::path::Path; use ecow::eco_vec; use tiny_skia as sk; -use typst::diag::SourceDiagnostic; -use typst::eval::Tracer; +use typst::diag::{SourceDiagnostic, Warned}; use typst::foundations::Smart; use typst::layout::{Abs, Frame, FrameItem, Page, Transform}; use typst::model::Document; @@ -80,13 +79,12 @@ impl<'a> Runner<'a> { log!(into: self.result.infos, "tree: {:#?}", self.test.source.root()); } - let mut tracer = Tracer::new(); - let (doc, errors) = match typst::compile(&self.world, &mut tracer) { + let Warned { output, warnings } = typst::compile(&self.world); + let (doc, errors) = match output { Ok(doc) => (Some(doc), eco_vec![]), Err(errors) => (None, errors), }; - let warnings = tracer.warnings(); if doc.is_none() && errors.is_empty() { log!(self, "no document, but also no errors"); }