More general evaluation interface

This commit is contained in:
Laurenz 2022-11-23 10:54:25 +01:00
parent 65aa27014d
commit b2a3d3f235
13 changed files with 100 additions and 95 deletions

View File

@ -235,10 +235,12 @@ fn typeset(command: TypesetCommand) -> StrResult<()> {
/// Typeset a single time. /// Typeset a single time.
fn typeset_once(world: &mut SystemWorld, command: &TypesetCommand) -> StrResult<()> { fn typeset_once(world: &mut SystemWorld, command: &TypesetCommand) -> StrResult<()> {
status(command, Status::Compiling).unwrap(); status(command, Status::Compiling).unwrap();
world.reset(); world.reset();
let main = world.resolve(&command.input).map_err(|err| err.to_string())?; 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. // Export the PDF.
Ok(frames) => { Ok(frames) => {
let buffer = typst::export::pdf(&frames); let buffer = typst::export::pdf(&frames);

View File

@ -11,7 +11,7 @@ pub use self::data::*;
pub use self::string::*; pub use self::string::*;
use comemo::Track; use comemo::Track;
use typst::model::{Eval, Route, Scopes, Vm}; use typst::model::{self, Route, Vm};
use typst::syntax::Source; use typst::syntax::Source;
use crate::prelude::*; use crate::prelude::*;
@ -33,22 +33,8 @@ pub fn assert(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
/// Evaluate a string as Typst markup. /// Evaluate a string as Typst markup.
pub fn eval(vm: &mut Vm, args: &mut Args) -> SourceResult<Value> { pub fn eval(vm: &mut Vm, args: &mut Args) -> SourceResult<Value> {
let Spanned { v: text, span } = args.expect::<Spanned<String>>("source")?; let Spanned { v: text, span } = args.expect::<Spanned<String>>("source")?;
// Parse the source and set a synthetic span for all nodes.
let source = Source::synthesized(text, span); 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 route = Route::default();
let mut sub = Vm::new(vm.world, route.track(), None, scopes); let module = model::eval(vm.world, route.track(), &source)?;
let result = ast.eval(&mut sub); Ok(Value::Content(module.content))
// Handle control flow.
if let Some(flow) = sub.flow {
bail!(flow.forbidden());
}
Ok(Value::Content(result?))
} }

View File

@ -154,7 +154,7 @@ pub fn styles() -> StyleMap {
/// Construct the standard lang item mapping. /// Construct the standard lang item mapping.
pub fn items() -> LangItems { pub fn items() -> LangItems {
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), em: |styles| styles.get(text::TextNode::SIZE),
dir: |styles| styles.get(text::TextNode::DIR), dir: |styles| styles.get(text::TextNode::DIR),
space: || text::SpaceNode.pack(), space: || text::SpaceNode.pack(),

View File

@ -49,6 +49,9 @@ pub use crate::__error as error;
pub type SourceResult<T> = Result<T, Box<Vec<SourceError>>>; pub type SourceResult<T> = Result<T, Box<Vec<SourceError>>>;
/// An error in a source file. /// 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)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct SourceError { pub struct SourceError {
/// The span of the erroneous node in the source code. /// The span of the erroneous node in the source code.

View File

@ -60,14 +60,19 @@ use crate::util::Buffer;
/// information. /// information.
pub fn typeset( pub fn typeset(
world: &(dyn World + 'static), world: &(dyn World + 'static),
main: SourceId, source: &Source,
) -> SourceResult<Vec<Frame>> { ) -> SourceResult<Vec<Frame>> {
// Set up the language items.
let config = world.config(); let config = world.config();
crate::model::set_lang_items(config.items); crate::model::set_lang_items(config.items);
// Evaluate the source file into a module.
let route = Route::default(); 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); 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. /// The environment in which typesetting occurs.

View File

@ -13,7 +13,7 @@ use super::{
use crate::diag::{bail, error, At, SourceResult, StrResult, Trace, Tracepoint}; use crate::diag::{bail, error, At, SourceResult, StrResult, Trace, Tracepoint};
use crate::geom::{Abs, Angle, Em, Fr, Ratio}; use crate::geom::{Abs, Angle, Em, Fr, Ratio};
use crate::syntax::ast::AstNode; 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::util::{format_eco, EcoString};
use crate::World; use crate::World;
@ -26,9 +26,10 @@ use crate::World;
pub fn eval( pub fn eval(
world: Tracked<dyn World>, world: Tracked<dyn World>,
route: Tracked<Route>, route: Tracked<Route>,
id: SourceId, source: &Source,
) -> SourceResult<Module> { ) -> SourceResult<Module> {
// Prevent cyclic evaluation. // Prevent cyclic evaluation.
let id = source.id();
if route.contains(id) { if route.contains(id) {
let path = world.source(id).path().display(); let path = world.source(id).path().display();
panic!("Tried to cyclicly evaluate {}", path); panic!("Tried to cyclicly evaluate {}", path);
@ -36,11 +37,10 @@ pub fn eval(
// Evaluate the module. // Evaluate the module.
let route = unsafe { Route::insert(route, id) }; let route = unsafe { Route::insert(route, id) };
let ast = world.source(id).ast()?;
let std = &world.config().scope; let std = &world.config().scope;
let scopes = Scopes::new(Some(std)); let scopes = Scopes::new(Some(std));
let mut vm = Vm::new(world, route.track(), Some(id), scopes); let mut vm = Vm::new(world, route.track(), id, scopes);
let result = ast.eval(&mut vm); let result = source.ast()?.eval(&mut vm);
// Handle control flow. // Handle control flow.
if let Some(flow) = vm.flow { if let Some(flow) = vm.flow {
@ -59,9 +59,9 @@ pub struct Route {
} }
impl Route { impl Route {
/// Create a new, empty route. /// Create a new route with just one entry.
pub fn new(id: Option<SourceId>) -> Self { pub fn new(id: SourceId) -> Self {
Self { id, parent: None } Self { id: Some(id), parent: None }
} }
/// Insert a new id into the route. /// Insert a new id into the route.
@ -94,7 +94,7 @@ pub struct Module {
} }
/// Evaluate an expression. /// Evaluate an expression.
pub trait Eval { pub(super) trait Eval {
/// The output of evaluating the expression. /// The output of evaluating the expression.
type Output; type Output;
@ -1038,10 +1038,9 @@ fn import(vm: &mut Vm, path: &str, span: Span) -> SourceResult<Module> {
} }
// Evaluate the file. // Evaluate the file.
let module = let source = vm.world.source(id);
eval(vm.world, vm.route, id).trace(vm.world, || Tracepoint::Import, span)?; let point = || Tracepoint::Import;
eval(vm.world, vm.route, source).trace(vm.world, point, span)
Ok(module)
} }
impl Eval for ast::LoopBreak { impl Eval for ast::LoopBreak {

View File

@ -97,7 +97,9 @@ impl Func {
args: Args, args: Args,
) -> SourceResult<Value> { ) -> SourceResult<Value> {
let route = Route::default(); 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) self.call(&mut vm, args)
} }
@ -178,7 +180,7 @@ impl Hash for Native {
#[derive(Hash)] #[derive(Hash)]
pub struct Closure { pub struct Closure {
/// The source file where the closure was defined. /// The source file where the closure was defined.
pub location: Option<SourceId>, pub location: SourceId,
/// The name of the closure. /// The name of the closure.
pub name: Option<EcoString>, pub name: Option<EcoString>,
/// Captured values from outer scopes. /// Captured values from outer scopes.
@ -219,7 +221,7 @@ impl Closure {
} }
// Determine the route inside the 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 fresh = Route::new(self.location);
let route = if detached { fresh.track() } else { vm.route }; let route = if detached { fresh.track() } else { vm.route };

View File

@ -42,7 +42,7 @@ macro_rules! item {
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct LangItems { pub struct LangItems {
/// The root layout function. /// The root layout function.
pub root: fn( pub layout: fn(
content: &Content, content: &Content,
world: Tracked<dyn World>, world: Tracked<dyn World>,
styles: StyleChain, styles: StyleChain,
@ -104,7 +104,7 @@ impl Debug for LangItems {
impl Hash for LangItems { impl Hash for LangItems {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
(self.root as usize).hash(state); (self.layout as usize).hash(state);
(self.em as usize).hash(state); (self.em as usize).hash(state);
(self.dir as usize).hash(state); (self.dir as usize).hash(state);
self.space.hash(state); self.space.hash(state);

View File

@ -15,7 +15,7 @@ pub struct Vm<'a> {
/// The route of source ids the VM took to reach its current location. /// The route of source ids the VM took to reach its current location.
pub route: Tracked<'a, Route>, pub route: Tracked<'a, Route>,
/// The current location. /// The current location.
pub location: Option<SourceId>, pub location: SourceId,
/// The stack of scopes. /// The stack of scopes.
pub scopes: Scopes<'a>, pub scopes: Scopes<'a>,
/// A control flow event that is currently happening. /// A control flow event that is currently happening.
@ -29,7 +29,7 @@ impl<'a> Vm<'a> {
pub fn new( pub fn new(
world: Tracked<'a, dyn World>, world: Tracked<'a, dyn World>,
route: Tracked<'a, Route>, route: Tracked<'a, Route>,
location: Option<SourceId>, location: SourceId,
scopes: Scopes<'a>, scopes: Scopes<'a>,
) -> Self { ) -> Self {
Self { Self {
@ -45,12 +45,12 @@ impl<'a> Vm<'a> {
/// Resolve a user-entered path to be relative to the compilation /// Resolve a user-entered path to be relative to the compilation
/// environment's root. /// environment's root.
pub fn locate(&self, path: &str) -> StrResult<PathBuf> { pub fn locate(&self, path: &str) -> StrResult<PathBuf> {
if let Some(id) = self.location { if !self.location.is_detached() {
if let Some(path) = path.strip_prefix('/') { if let Some(path) = path.strip_prefix('/') {
return Ok(self.world.config().root.join(path).normalize()); 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()); return Ok(dir.join(path).normalize());
} }
} }

View File

@ -21,26 +21,21 @@ use crate::util::{PathExt, StrExt};
pub struct Source { pub struct Source {
id: SourceId, id: SourceId,
path: PathBuf, path: PathBuf,
text: Prehashed<String>,
lines: Vec<Line>, lines: Vec<Line>,
text: Prehashed<String>,
root: Prehashed<SyntaxNode>, root: Prehashed<SyntaxNode>,
} }
impl Source { impl Source {
/// Create a new source file. /// Create a new source file.
pub fn new(id: SourceId, path: &Path, text: String) -> Self { 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); let mut root = parse(&text);
root.numberize(id, Span::FULL).unwrap(); root.numberize(id, Span::FULL).unwrap();
Self { Self {
id, id,
path: path.normalize(), path: path.normalize(),
lines: lines(&text),
text: Prehashed::new(text), text: Prehashed::new(text),
lines,
root: Prehashed::new(root), root: Prehashed::new(root),
} }
} }
@ -51,13 +46,16 @@ impl Source {
} }
/// Create a source file with the same synthetic span for all nodes. /// Create a source file with the same synthetic span for all nodes.
pub fn synthesized(text: impl Into<String>, span: Span) -> Self { pub fn synthesized(text: String, span: Span) -> Self {
let mut file = Self::detached(text); let mut root = parse(&text);
let mut root = file.root.into_inner();
root.synthesize(span); root.synthesize(span);
file.root = Prehashed::new(root); Self {
file.id = span.source(); id: SourceId::detached(),
file path: PathBuf::new(),
lines: lines(&text),
text: Prehashed::new(text),
root: Prehashed::new(root),
}
} }
/// The root node of the file's untyped syntax tree. /// The root node of the file's untyped syntax tree.
@ -98,10 +96,9 @@ impl Source {
/// Fully replace the source text. /// Fully replace the source text.
pub fn replace(&mut self, text: String) { pub fn replace(&mut self, text: String) {
self.text = Prehashed::new(text); self.text = Prehashed::new(text);
self.lines = vec![Line { byte_idx: 0, utf16_idx: 0 }]; self.lines = lines(&self.text);
self.lines.extend(lines(0, 0, &self.text));
let mut root = parse(&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); self.root = Prehashed::new(root);
} }
@ -128,7 +125,7 @@ impl Source {
// Recalculate the line starts after the edit. // Recalculate the line starts after the edit.
self.lines self.lines
.extend(lines(start_byte, start_utf16, &self.text[start_byte..])); .extend(lines_from(start_byte, start_utf16, &self.text[start_byte..]));
// Incrementally reparse the replaced range. // Incrementally reparse the replaced range.
let mut root = std::mem::take(&mut self.root).into_inner(); let mut root = std::mem::take(&mut self.root).into_inner();
@ -262,11 +259,16 @@ impl Hash for Source {
pub struct SourceId(u16); pub struct SourceId(u16);
impl SourceId { 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 { pub const fn detached() -> Self {
Self(u16::MAX) 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. /// Create a source id from a number.
pub const fn from_u16(v: u16) -> Self { pub const fn from_u16(v: u16) -> Self {
Self(v) Self(v)
@ -287,8 +289,15 @@ struct Line {
utf16_idx: usize, utf16_idx: usize,
} }
/// Iterate over the lines in the string. /// Create a line vector.
fn lines( fn lines(text: &str) -> Vec<Line> {
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, byte_offset: usize,
utf16_offset: usize, utf16_offset: usize,
text: &str, text: &str,

View File

@ -27,15 +27,13 @@ use super::SourceId;
pub struct Span(NonZeroU64); pub struct Span(NonZeroU64);
impl Span { 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. /// The full range of numbers available for span numbering.
pub const FULL: Range<u64> = 2..(1 << Self::BITS); pub const FULL: Range<u64> = 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. /// Create a new span from a source id and a unique number.
/// ///
@ -46,13 +44,26 @@ impl Span {
"span number outside valid range" "span number outside valid range"
); );
let bits = ((id.into_u16() as u64) << Self::BITS) | number; Self::pack(id, number)
Self(to_non_zero(bits))
} }
/// A span that does not point into any source file. /// A span that does not point into any source file.
pub const fn detached() -> Self { 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. /// 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. /// A value with a span locating it in the source code.
#[derive(Copy, Clone, Eq, PartialEq, Hash)] #[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct Spanned<T> { pub struct Spanned<T> {

View File

@ -74,21 +74,18 @@ fn bench_highlight(iai: &mut Iai) {
fn bench_eval(iai: &mut Iai) { fn bench_eval(iai: &mut Iai) {
let world = BenchWorld::new(); let world = BenchWorld::new();
let id = world.source.id();
let route = typst::model::Route::default(); 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) { fn bench_typeset(iai: &mut Iai) {
let world = BenchWorld::new(); let world = BenchWorld::new();
let id = world.source.id(); iai.run(|| typst::typeset(&world, &world.source));
iai.run(|| typst::typeset(&world, id));
} }
fn bench_render(iai: &mut Iai) { fn bench_render(iai: &mut Iai) {
let world = BenchWorld::new(); let world = BenchWorld::new();
let id = world.source.id(); let frames = typst::typeset(&world, &world.source).unwrap();
let frames = typst::typeset(&world, id).unwrap();
iai.run(|| typst::export::render(&frames[0], 1.0)) iai.run(|| typst::export::render(&frames[0], 1.0))
} }
@ -110,8 +107,7 @@ impl BenchWorld {
let font = Font::new(FONT.into(), 0).unwrap(); let font = Font::new(FONT.into(), 0).unwrap();
let book = FontBook::from_fonts([&font]); let book = FontBook::from_fonts([&font]);
let id = SourceId::from_u16(0); let source = Source::detached(TEXT);
let source = Source::new(id, Path::new("bench.typ"), TEXT.into());
Self { Self {
config: Prehashed::new(config), config: Prehashed::new(config),
@ -148,6 +144,6 @@ impl World for BenchWorld {
} }
fn source(&self, _: SourceId) -> &Source { fn source(&self, _: SourceId) -> &Source {
&self.source unimplemented!()
} }
} }

View File

@ -418,13 +418,13 @@ fn test_part(
ok &= test_reparse(world.source(id).text(), i, rng); ok &= test_reparse(world.source(id).text(), i, rng);
if world.print.model { 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 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); 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![]), Ok(frames) => (frames, vec![]),
Err(errors) => (vec![], *errors), Err(errors) => (vec![], *errors),
}; };