diff --git a/cli/src/main.rs b/cli/src/main.rs index 8030a82d6..f7fd5903f 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -235,10 +235,12 @@ fn typeset(command: TypesetCommand) -> StrResult<()> { /// Typeset a single time. fn typeset_once(world: &mut SystemWorld, command: &TypesetCommand) -> StrResult<()> { status(command, Status::Compiling).unwrap(); - world.reset(); + let main = world.resolve(&command.input).map_err(|err| err.to_string())?; - match typst::typeset(world, main) { + let source = world.source(main); + + match typst::typeset(world, source) { // Export the PDF. Ok(frames) => { let buffer = typst::export::pdf(&frames); diff --git a/library/src/base/mod.rs b/library/src/base/mod.rs index f1fdd2b90..837a2f0e8 100644 --- a/library/src/base/mod.rs +++ b/library/src/base/mod.rs @@ -11,7 +11,7 @@ pub use self::data::*; pub use self::string::*; use comemo::Track; -use typst::model::{Eval, Route, Scopes, Vm}; +use typst::model::{self, Route, Vm}; use typst::syntax::Source; use crate::prelude::*; @@ -33,22 +33,8 @@ pub fn assert(_: &mut Vm, args: &mut Args) -> SourceResult { /// Evaluate a string as Typst markup. pub fn eval(vm: &mut Vm, args: &mut Args) -> SourceResult { let Spanned { v: text, span } = args.expect::>("source")?; - - // Parse the source and set a synthetic span for all nodes. let source = Source::synthesized(text, span); - let ast = source.ast()?; - - // Evaluate the source. - let std = &vm.world.config().scope; - let scopes = Scopes::new(Some(std)); let route = Route::default(); - let mut sub = Vm::new(vm.world, route.track(), None, scopes); - let result = ast.eval(&mut sub); - - // Handle control flow. - if let Some(flow) = sub.flow { - bail!(flow.forbidden()); - } - - Ok(Value::Content(result?)) + let module = model::eval(vm.world, route.track(), &source)?; + Ok(Value::Content(module.content)) } diff --git a/library/src/lib.rs b/library/src/lib.rs index 7a292c6dd..e5e60968b 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -154,7 +154,7 @@ pub fn styles() -> StyleMap { /// Construct the standard lang item mapping. pub fn items() -> LangItems { LangItems { - root: |content, world, styles| content.layout_root(world, styles), + layout: |content, world, styles| content.layout_root(world, styles), em: |styles| styles.get(text::TextNode::SIZE), dir: |styles| styles.get(text::TextNode::DIR), space: || text::SpaceNode.pack(), diff --git a/src/diag.rs b/src/diag.rs index 613789f8b..99ef12914 100644 --- a/src/diag.rs +++ b/src/diag.rs @@ -49,6 +49,9 @@ pub use crate::__error as error; pub type SourceResult = Result>>; /// An error in a source file. +/// +/// This contained spans will only be detached if any of the input source files +/// were detached. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct SourceError { /// The span of the erroneous node in the source code. diff --git a/src/lib.rs b/src/lib.rs index bb3a1c027..319c13aba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,14 +60,19 @@ use crate::util::Buffer; /// information. pub fn typeset( world: &(dyn World + 'static), - main: SourceId, + source: &Source, ) -> SourceResult> { + // Set up the language items. let config = world.config(); crate::model::set_lang_items(config.items); + + // Evaluate the source file into a module. let route = Route::default(); - let module = model::eval(world.track(), route.track(), main)?; + let module = model::eval(world.track(), route.track(), source)?; + + // Layout the module's contents. let styles = StyleChain::with_root(&config.styles); - item!(root)(&module.content, world.track(), styles) + item!(layout)(&module.content, world.track(), styles) } /// The environment in which typesetting occurs. diff --git a/src/model/eval.rs b/src/model/eval.rs index 7c0cffb59..7f2cea639 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -13,7 +13,7 @@ use super::{ use crate::diag::{bail, error, At, SourceResult, StrResult, Trace, Tracepoint}; use crate::geom::{Abs, Angle, Em, Fr, Ratio}; use crate::syntax::ast::AstNode; -use crate::syntax::{ast, SourceId, Span, Spanned, Unit}; +use crate::syntax::{ast, Source, SourceId, Span, Spanned, Unit}; use crate::util::{format_eco, EcoString}; use crate::World; @@ -26,9 +26,10 @@ use crate::World; pub fn eval( world: Tracked, route: Tracked, - id: SourceId, + source: &Source, ) -> SourceResult { // Prevent cyclic evaluation. + let id = source.id(); if route.contains(id) { let path = world.source(id).path().display(); panic!("Tried to cyclicly evaluate {}", path); @@ -36,11 +37,10 @@ pub fn eval( // Evaluate the module. let route = unsafe { Route::insert(route, id) }; - let ast = world.source(id).ast()?; let std = &world.config().scope; let scopes = Scopes::new(Some(std)); - let mut vm = Vm::new(world, route.track(), Some(id), scopes); - let result = ast.eval(&mut vm); + let mut vm = Vm::new(world, route.track(), id, scopes); + let result = source.ast()?.eval(&mut vm); // Handle control flow. if let Some(flow) = vm.flow { @@ -59,9 +59,9 @@ pub struct Route { } impl Route { - /// Create a new, empty route. - pub fn new(id: Option) -> Self { - Self { id, parent: None } + /// Create a new route with just one entry. + pub fn new(id: SourceId) -> Self { + Self { id: Some(id), parent: None } } /// Insert a new id into the route. @@ -94,7 +94,7 @@ pub struct Module { } /// Evaluate an expression. -pub trait Eval { +pub(super) trait Eval { /// The output of evaluating the expression. type Output; @@ -1038,10 +1038,9 @@ fn import(vm: &mut Vm, path: &str, span: Span) -> SourceResult { } // Evaluate the file. - let module = - eval(vm.world, vm.route, id).trace(vm.world, || Tracepoint::Import, span)?; - - Ok(module) + let source = vm.world.source(id); + let point = || Tracepoint::Import; + eval(vm.world, vm.route, source).trace(vm.world, point, span) } impl Eval for ast::LoopBreak { diff --git a/src/model/func.rs b/src/model/func.rs index f313dcdaf..d84a81708 100644 --- a/src/model/func.rs +++ b/src/model/func.rs @@ -97,7 +97,9 @@ impl Func { args: Args, ) -> SourceResult { let route = Route::default(); - let mut vm = Vm::new(world, route.track(), None, Scopes::new(None)); + let id = SourceId::detached(); + let scopes = Scopes::new(None); + let mut vm = Vm::new(world, route.track(), id, scopes); self.call(&mut vm, args) } @@ -178,7 +180,7 @@ impl Hash for Native { #[derive(Hash)] pub struct Closure { /// The source file where the closure was defined. - pub location: Option, + pub location: SourceId, /// The name of the closure. pub name: Option, /// Captured values from outer scopes. @@ -219,7 +221,7 @@ impl Closure { } // Determine the route inside the closure. - let detached = vm.location.is_none(); + let detached = vm.location.is_detached(); let fresh = Route::new(self.location); let route = if detached { fresh.track() } else { vm.route }; diff --git a/src/model/items.rs b/src/model/items.rs index 771756fe8..50ea8b602 100644 --- a/src/model/items.rs +++ b/src/model/items.rs @@ -42,7 +42,7 @@ macro_rules! item { #[derive(Copy, Clone)] pub struct LangItems { /// The root layout function. - pub root: fn( + pub layout: fn( content: &Content, world: Tracked, styles: StyleChain, @@ -104,7 +104,7 @@ impl Debug for LangItems { impl Hash for LangItems { fn hash(&self, state: &mut H) { - (self.root as usize).hash(state); + (self.layout as usize).hash(state); (self.em as usize).hash(state); (self.dir as usize).hash(state); self.space.hash(state); diff --git a/src/model/vm.rs b/src/model/vm.rs index db0bf77cb..28885f294 100644 --- a/src/model/vm.rs +++ b/src/model/vm.rs @@ -15,7 +15,7 @@ pub struct Vm<'a> { /// The route of source ids the VM took to reach its current location. pub route: Tracked<'a, Route>, /// The current location. - pub location: Option, + pub location: SourceId, /// The stack of scopes. pub scopes: Scopes<'a>, /// A control flow event that is currently happening. @@ -29,7 +29,7 @@ impl<'a> Vm<'a> { pub fn new( world: Tracked<'a, dyn World>, route: Tracked<'a, Route>, - location: Option, + location: SourceId, scopes: Scopes<'a>, ) -> Self { Self { @@ -45,12 +45,12 @@ impl<'a> Vm<'a> { /// Resolve a user-entered path to be relative to the compilation /// environment's root. pub fn locate(&self, path: &str) -> StrResult { - if let Some(id) = self.location { + if !self.location.is_detached() { if let Some(path) = path.strip_prefix('/') { return Ok(self.world.config().root.join(path).normalize()); } - if let Some(dir) = self.world.source(id).path().parent() { + if let Some(dir) = self.world.source(self.location).path().parent() { return Ok(dir.join(path).normalize()); } } diff --git a/src/syntax/source.rs b/src/syntax/source.rs index 08b526859..6144d5241 100644 --- a/src/syntax/source.rs +++ b/src/syntax/source.rs @@ -21,26 +21,21 @@ use crate::util::{PathExt, StrExt}; pub struct Source { id: SourceId, path: PathBuf, - text: Prehashed, lines: Vec, + text: Prehashed, root: Prehashed, } impl Source { /// Create a new source file. pub fn new(id: SourceId, path: &Path, text: String) -> Self { - let lines = std::iter::once(Line { byte_idx: 0, utf16_idx: 0 }) - .chain(lines(0, 0, &text)) - .collect(); - let mut root = parse(&text); root.numberize(id, Span::FULL).unwrap(); - Self { id, path: path.normalize(), + lines: lines(&text), text: Prehashed::new(text), - lines, root: Prehashed::new(root), } } @@ -51,13 +46,16 @@ impl Source { } /// Create a source file with the same synthetic span for all nodes. - pub fn synthesized(text: impl Into, span: Span) -> Self { - let mut file = Self::detached(text); - let mut root = file.root.into_inner(); + pub fn synthesized(text: String, span: Span) -> Self { + let mut root = parse(&text); root.synthesize(span); - file.root = Prehashed::new(root); - file.id = span.source(); - file + Self { + id: SourceId::detached(), + path: PathBuf::new(), + lines: lines(&text), + text: Prehashed::new(text), + root: Prehashed::new(root), + } } /// The root node of the file's untyped syntax tree. @@ -98,10 +96,9 @@ impl Source { /// Fully replace the source text. pub fn replace(&mut self, text: String) { self.text = Prehashed::new(text); - self.lines = vec![Line { byte_idx: 0, utf16_idx: 0 }]; - self.lines.extend(lines(0, 0, &self.text)); + self.lines = lines(&self.text); let mut root = parse(&self.text); - root.numberize(self.id(), Span::FULL).unwrap(); + root.numberize(self.id, Span::FULL).unwrap(); self.root = Prehashed::new(root); } @@ -128,7 +125,7 @@ impl Source { // Recalculate the line starts after the edit. self.lines - .extend(lines(start_byte, start_utf16, &self.text[start_byte..])); + .extend(lines_from(start_byte, start_utf16, &self.text[start_byte..])); // Incrementally reparse the replaced range. let mut root = std::mem::take(&mut self.root).into_inner(); @@ -262,11 +259,16 @@ impl Hash for Source { pub struct SourceId(u16); impl SourceId { - /// Create a new source id for a file that is not part of a store. + /// Create a new source id for a file that is not part of the world. pub const fn detached() -> Self { Self(u16::MAX) } + /// Whether the source id is the detached. + pub const fn is_detached(self) -> bool { + self.0 == Self::detached().0 + } + /// Create a source id from a number. pub const fn from_u16(v: u16) -> Self { Self(v) @@ -287,8 +289,15 @@ struct Line { utf16_idx: usize, } -/// Iterate over the lines in the string. -fn lines( +/// Create a line vector. +fn lines(text: &str) -> Vec { + std::iter::once(Line { byte_idx: 0, utf16_idx: 0 }) + .chain(lines_from(0, 0, &text)) + .collect() +} + +/// Compute a line iterator from an offset. +fn lines_from( byte_offset: usize, utf16_offset: usize, text: &str, diff --git a/src/syntax/span.rs b/src/syntax/span.rs index 7fbb73052..22ada3593 100644 --- a/src/syntax/span.rs +++ b/src/syntax/span.rs @@ -27,15 +27,13 @@ use super::SourceId; pub struct Span(NonZeroU64); impl Span { - // Data layout: - // | 16 bits source id | 48 bits number | - - // Number of bits for and minimum and maximum numbers assignable to spans. - const BITS: usize = 48; - const DETACHED: u64 = 1; - /// The full range of numbers available for span numbering. pub const FULL: Range = 2..(1 << Self::BITS); + const DETACHED: u64 = 1; + + // Data layout: + // | 16 bits source id | 48 bits number | + const BITS: usize = 48; /// Create a new span from a source id and a unique number. /// @@ -46,13 +44,26 @@ impl Span { "span number outside valid range" ); - let bits = ((id.into_u16() as u64) << Self::BITS) | number; - Self(to_non_zero(bits)) + Self::pack(id, number) } /// A span that does not point into any source file. pub const fn detached() -> Self { - Self(to_non_zero(Self::DETACHED)) + Self::pack(SourceId::detached(), Self::DETACHED) + } + + /// Pack the components into a span. + const fn pack(id: SourceId, number: u64) -> Span { + let bits = ((id.into_u16() as u64) << Self::BITS) | number; + match NonZeroU64::new(bits) { + Some(v) => Self(v), + None => panic!("span encoding is zero"), + } + } + + /// Whether the span is detached. + pub const fn is_detached(self) -> bool { + self.source().is_detached() } /// The id of the source file the span points into. @@ -66,14 +77,6 @@ impl Span { } } -/// Convert to a non zero u64. -const fn to_non_zero(v: u64) -> NonZeroU64 { - match NonZeroU64::new(v) { - Some(v) => v, - None => panic!("span encoding is zero"), - } -} - /// A value with a span locating it in the source code. #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct Spanned { diff --git a/tests/src/benches.rs b/tests/src/benches.rs index 5c9b54336..29117a909 100644 --- a/tests/src/benches.rs +++ b/tests/src/benches.rs @@ -74,21 +74,18 @@ fn bench_highlight(iai: &mut Iai) { fn bench_eval(iai: &mut Iai) { let world = BenchWorld::new(); - let id = world.source.id(); let route = typst::model::Route::default(); - iai.run(|| typst::model::eval(world.track(), route.track(), id).unwrap()); + iai.run(|| typst::model::eval(world.track(), route.track(), &world.source).unwrap()); } fn bench_typeset(iai: &mut Iai) { let world = BenchWorld::new(); - let id = world.source.id(); - iai.run(|| typst::typeset(&world, id)); + iai.run(|| typst::typeset(&world, &world.source)); } fn bench_render(iai: &mut Iai) { let world = BenchWorld::new(); - let id = world.source.id(); - let frames = typst::typeset(&world, id).unwrap(); + let frames = typst::typeset(&world, &world.source).unwrap(); iai.run(|| typst::export::render(&frames[0], 1.0)) } @@ -110,8 +107,7 @@ impl BenchWorld { let font = Font::new(FONT.into(), 0).unwrap(); let book = FontBook::from_fonts([&font]); - let id = SourceId::from_u16(0); - let source = Source::new(id, Path::new("bench.typ"), TEXT.into()); + let source = Source::detached(TEXT); Self { config: Prehashed::new(config), @@ -148,6 +144,6 @@ impl World for BenchWorld { } fn source(&self, _: SourceId) -> &Source { - &self.source + unimplemented!() } } diff --git a/tests/src/tests.rs b/tests/src/tests.rs index 52cc54db1..89c83857e 100644 --- a/tests/src/tests.rs +++ b/tests/src/tests.rs @@ -418,13 +418,13 @@ fn test_part( ok &= test_reparse(world.source(id).text(), i, rng); if world.print.model { - let tracked = (world as &dyn World).track(); + let world = (world as &dyn World).track(); let route = typst::model::Route::default(); - let module = typst::model::eval(tracked, route.track(), id).unwrap(); + let module = typst::model::eval(world, route.track(), source).unwrap(); println!("Model:\n{:#?}\n", module.content); } - let (mut frames, errors) = match typst::typeset(world, id) { + let (mut frames, errors) = match typst::typeset(world, source) { Ok(frames) => (frames, vec![]), Err(errors) => (vec![], *errors), };