summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-06-01 12:46:01 +0200
committerLaurenz <laurmaedje@gmail.com>2021-06-01 12:55:07 +0200
commit7218892c722ca583297c0ebbda350bdf6f16d3ce (patch)
tree27ebbfaf0662c1e0dd01e7c5e9e360ab288cae4d /src
parent9bdb0bdeffa5e4b6da9e3f6d3c1b79c506005fc5 (diff)
Refactor path handling
Diffstat (limited to 'src')
-rw-r--r--src/eval/capture.rs2
-rw-r--r--src/eval/mod.rs46
-rw-r--r--src/eval/scope.rs11
-rw-r--r--src/eval/value.rs1
-rw-r--r--src/exec/mod.rs9
-rw-r--r--src/export/pdf.rs6
-rw-r--r--src/font.rs14
-rw-r--r--src/image.rs42
-rw-r--r--src/lib.rs23
-rw-r--r--src/library/markup.rs2
-rw-r--r--src/loading/fs.rs44
-rw-r--r--src/loading/mod.rs14
-rw-r--r--src/main.rs16
-rw-r--r--src/util.rs26
14 files changed, 141 insertions, 115 deletions
diff --git a/src/eval/capture.rs b/src/eval/capture.rs
index bee523ef..74da4747 100644
--- a/src/eval/capture.rs
+++ b/src/eval/capture.rs
@@ -17,7 +17,7 @@ impl<'a> CapturesVisitor<'a> {
pub fn new(external: &'a Scopes) -> Self {
Self {
external,
- internal: Scopes::new(),
+ internal: Scopes::new(None),
captures: Scope::new(),
}
}
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 38c6263e..c480ddfe 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -23,22 +23,17 @@ use crate::loading::{FileHash, Loader};
use crate::parse::parse;
use crate::syntax::visit::Visit;
use crate::syntax::*;
+use crate::util::PathExt;
-/// Evaluated a parsed source file into a module.
-///
-/// The `path` should point to the source file for the `tree` and is used to
-/// resolve relative path names.
-///
-/// The `scope` consists of the base definitions that are present from the
-/// beginning (typically, the standard library).
+/// Evaluate a parsed source file into a module.
pub fn eval(
loader: &mut dyn Loader,
cache: &mut Cache,
- path: &Path,
+ path: Option<&Path>,
tree: Rc<Tree>,
- base: &Scope,
+ scope: &Scope,
) -> Pass<Module> {
- let mut ctx = EvalContext::new(loader, cache, path, base);
+ let mut ctx = EvalContext::new(loader, cache, path, scope);
let map = tree.eval(&mut ctx);
let module = Module {
scope: ctx.scopes.top,
@@ -67,7 +62,7 @@ pub struct EvalContext<'a> {
/// Evaluation diagnostics.
pub diags: DiagSet,
/// The location of the currently evaluated file.
- pub path: PathBuf,
+ pub path: Option<PathBuf>,
/// The stack of imported files that led to evaluation of the current file.
pub route: Vec<FileHash>,
/// A map of loaded module.
@@ -79,20 +74,24 @@ impl<'a> EvalContext<'a> {
pub fn new(
loader: &'a mut dyn Loader,
cache: &'a mut Cache,
- path: &Path,
- base: &'a Scope,
+ path: Option<&Path>,
+ scope: &'a Scope,
) -> Self {
+ let path = path.map(PathExt::normalize);
+
let mut route = vec![];
- if let Some(hash) = loader.resolve(path) {
- route.push(hash);
+ if let Some(path) = &path {
+ if let Some(hash) = loader.resolve(path) {
+ route.push(hash);
+ }
}
Self {
loader,
cache,
- scopes: Scopes::with_base(Some(base)),
+ scopes: Scopes::new(Some(scope)),
diags: DiagSet::new(),
- path: path.to_owned(),
+ path,
route,
modules: HashMap::new(),
}
@@ -102,10 +101,13 @@ impl<'a> EvalContext<'a> {
///
/// Generates an error if the file is not found.
pub fn resolve(&mut self, path: &str, span: Span) -> Option<(PathBuf, FileHash)> {
- let dir = self.path.parent().expect("location is a file");
- let path = dir.join(path);
+ let path = match &self.path {
+ Some(current) => current.parent()?.join(path),
+ None => PathBuf::from(path),
+ };
+
match self.loader.resolve(&path) {
- Some(hash) => Some((path, hash)),
+ Some(hash) => Some((path.normalize(), hash)),
None => {
self.diag(error!(span, "file not found"));
None
@@ -142,10 +144,10 @@ impl<'a> EvalContext<'a> {
let parsed = parse(string);
// Prepare the new context.
- let new_scopes = Scopes::with_base(self.scopes.base);
+ let new_scopes = Scopes::new(self.scopes.base);
let old_scopes = mem::replace(&mut self.scopes, new_scopes);
let old_diags = mem::replace(&mut self.diags, parsed.diags);
- let old_path = mem::replace(&mut self.path, resolved);
+ let old_path = mem::replace(&mut self.path, Some(resolved));
self.route.push(hash);
// Evaluate the module.
diff --git a/src/eval/scope.rs b/src/eval/scope.rs
index cfa2bccd..e5afb6b0 100644
--- a/src/eval/scope.rs
+++ b/src/eval/scope.rs
@@ -22,16 +22,7 @@ pub struct Scopes<'a> {
impl<'a> Scopes<'a> {
/// Create a new, empty hierarchy of scopes.
- pub fn new() -> Self {
- Self {
- top: Scope::new(),
- scopes: vec![],
- base: None,
- }
- }
-
- /// Create a new hierarchy of scopes with a base scope.
- pub fn with_base(base: Option<&'a Scope>) -> Self {
+ pub fn new(base: Option<&'a Scope>) -> Self {
Self { top: Scope::new(), scopes: vec![], base }
}
diff --git a/src/eval/value.rs b/src/eval/value.rs
index d10d734a..e2ff5383 100644
--- a/src/eval/value.rs
+++ b/src/eval/value.rs
@@ -664,7 +664,6 @@ impl From<AnyValue> for Value {
/// This would allow the type `FontFamily` to be cast from:
/// - a [`Value::Any`] variant already containing a `FontFamily`,
/// - a string, producing a named font family.
-#[macro_export]
macro_rules! value {
($type:ty:
$type_name:literal
diff --git a/src/exec/mod.rs b/src/exec/mod.rs
index 643d5b44..35e6b55c 100644
--- a/src/exec/mod.rs
+++ b/src/exec/mod.rs
@@ -15,9 +15,6 @@ use crate::pretty::pretty;
use crate::syntax::*;
/// Execute a template to produce a layout tree.
-///
-/// The `state` is the base state that may be updated over the course of
-/// execution.
pub fn exec(template: &TemplateValue, state: State) -> Pass<layout::Tree> {
let mut ctx = ExecContext::new(state);
template.exec(&mut ctx);
@@ -53,7 +50,7 @@ impl ExecWithMap for Tree {
impl ExecWithMap for Node {
fn exec_with_map(&self, ctx: &mut ExecContext, map: &NodeMap) {
match self {
- Node::Text(text) => ctx.push_text(text.clone()),
+ Node::Text(text) => ctx.push_text(text),
Node::Space => ctx.push_word_space(),
_ => map[&(self as *const _)].exec(ctx),
}
@@ -66,7 +63,7 @@ impl Exec for Value {
Value::None => {}
Value::Int(v) => ctx.push_text(pretty(v)),
Value::Float(v) => ctx.push_text(pretty(v)),
- Value::Str(v) => ctx.push_text(v.clone()),
+ Value::Str(v) => ctx.push_text(v),
Value::Template(v) => v.exec(ctx),
Value::Error => {}
other => {
@@ -93,7 +90,7 @@ impl Exec for TemplateNode {
fn exec(&self, ctx: &mut ExecContext) {
match self {
Self::Tree { tree, map } => tree.exec_with_map(ctx, &map),
- Self::Str(v) => ctx.push_text(v.clone()),
+ Self::Str(v) => ctx.push_text(v),
Self::Func(v) => v.exec(ctx),
}
}
diff --git a/src/export/pdf.rs b/src/export/pdf.rs
index 0ca4df38..1cc62332 100644
--- a/src/export/pdf.rs
+++ b/src/export/pdf.rs
@@ -139,7 +139,7 @@ impl<'a> PdfExporter<'a> {
// We only write font switching actions when the used face changes. To
// do that, we need to remember the active face.
- let mut face = FaceId::MAX;
+ let mut face = None;
let mut size = Length::zero();
let mut fill: Option<Fill> = None;
@@ -158,8 +158,8 @@ impl<'a> PdfExporter<'a> {
// Then, also check if we need to issue a font switching
// action.
- if shaped.face_id != face || shaped.size != size {
- face = shaped.face_id;
+ if face != Some(shaped.face_id) || shaped.size != size {
+ face = Some(shaped.face_id);
size = shaped.size;
let name = format!("F{}", self.fonts.map(shaped.face_id));
diff --git a/src/font.rs b/src/font.rs
index 69a30900..516d4bbe 100644
--- a/src/font.rs
+++ b/src/font.rs
@@ -6,8 +6,7 @@ use std::fmt::{self, Debug, Display, Formatter};
use serde::{Deserialize, Serialize};
use crate::geom::Length;
-use crate::loading::Buffer;
-use crate::loading::Loader;
+use crate::loading::{Buffer, Loader};
/// A font face.
pub struct Face {
@@ -171,7 +170,7 @@ impl FontCache {
let mut families = HashMap::<String, Vec<FaceId>>::new();
for (i, info) in loader.faces().iter().enumerate() {
- let id = FaceId(i as u32);
+ let id = FaceId(i as u64);
faces.push(None);
families
.entry(info.family.to_lowercase())
@@ -259,22 +258,19 @@ impl FontCache {
/// A unique identifier for a loaded font face.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
-pub struct FaceId(u32);
+pub struct FaceId(u64);
impl FaceId {
- /// A blank initialization value.
- pub const MAX: Self = Self(u32::MAX);
-
/// Create a face id from the raw underlying value.
///
/// This should only be called with values returned by
/// [`into_raw`](Self::into_raw).
- pub fn from_raw(v: u32) -> Self {
+ pub fn from_raw(v: u64) -> Self {
Self(v)
}
/// Convert into the raw underlying value.
- pub fn into_raw(self) -> u32 {
+ pub fn into_raw(self) -> u64 {
self.0
}
}
diff --git a/src/image.rs b/src/image.rs
index 3c5c8573..90252ff3 100644
--- a/src/image.rs
+++ b/src/image.rs
@@ -9,7 +9,7 @@ use image::io::Reader as ImageReader;
use image::{DynamicImage, GenericImageView, ImageFormat};
use serde::{Deserialize, Serialize};
-use crate::loading::{FileHash, Loader};
+use crate::loading::Loader;
/// A loaded image.
pub struct Image {
@@ -55,10 +55,8 @@ impl Debug for Image {
/// Caches decoded images.
pub struct ImageCache {
- /// Loaded images indexed by [`ImageId`].
- images: Vec<Image>,
/// Maps from file hashes to ids of decoded images.
- map: HashMap<FileHash, ImageId>,
+ images: HashMap<ImageId, Image>,
/// Callback for loaded images.
on_load: Option<Box<dyn Fn(ImageId, &Image)>>,
}
@@ -66,28 +64,22 @@ pub struct ImageCache {
impl ImageCache {
/// Create a new, empty image cache.
pub fn new() -> Self {
- Self {
- images: vec![],
- map: HashMap::new(),
- on_load: None,
- }
+ Self { images: HashMap::new(), on_load: None }
}
/// Load and decode an image file from a path.
pub fn load(&mut self, loader: &mut dyn Loader, path: &Path) -> Option<ImageId> {
- Some(match self.map.entry(loader.resolve(path)?) {
- Entry::Occupied(entry) => *entry.get(),
- Entry::Vacant(entry) => {
- let buffer = loader.load_file(path)?;
- let image = Image::parse(&buffer)?;
- let id = ImageId(self.images.len() as u32);
- if let Some(callback) = &self.on_load {
- callback(id, &image);
- }
- self.images.push(image);
- *entry.insert(id)
+ let hash = loader.resolve(path)?;
+ let id = ImageId(hash.into_raw());
+ if let Entry::Vacant(entry) = self.images.entry(id) {
+ let buffer = loader.load_file(path)?;
+ let image = Image::parse(&buffer)?;
+ if let Some(callback) = &self.on_load {
+ callback(id, &image);
}
- })
+ entry.insert(image);
+ }
+ Some(id)
}
/// Get a reference to a loaded image.
@@ -96,7 +88,7 @@ impl ImageCache {
/// only be called with ids returned by [`load()`](Self::load).
#[track_caller]
pub fn get(&self, id: ImageId) -> &Image {
- &self.images[id.0 as usize]
+ &self.images[&id]
}
/// Register a callback which is invoked each time an image is loaded.
@@ -110,19 +102,19 @@ impl ImageCache {
/// A unique identifier for a loaded image.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
-pub struct ImageId(u32);
+pub struct ImageId(u64);
impl ImageId {
/// Create an image id from the raw underlying value.
///
/// This should only be called with values returned by
/// [`into_raw`](Self::into_raw).
- pub fn from_raw(v: u32) -> Self {
+ pub fn from_raw(v: u64) -> Self {
Self(v)
}
/// Convert into the raw underlying value.
- pub fn into_raw(self) -> u32 {
+ pub fn into_raw(self) -> u64 {
self.0
}
}
diff --git a/src/lib.rs b/src/lib.rs
index 65e23c79..3c50230f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -59,16 +59,33 @@ use crate::layout::Frame;
use crate::loading::Loader;
/// Process source code directly into a collection of layouted frames.
+///
+/// # Parameters
+/// - The `loader` is used to load fonts, images and other source files.
+/// - The `cache` stores things that are reusable across several compilations
+/// like loaded fonts, decoded images and layouting artifacts.
+/// - The `path` should point to the source file if `src` comes from the file
+/// system and is used to resolve relative paths (for importing and image
+/// loading).
+/// - The `src` is the _Typst_ source code to typeset.
+/// - The `scope` contains definitions that are available everywhere,
+/// typically the standard library.
+/// - The `state` defines initial properties for page size, font selection and
+/// so on.
+///
+/// # Return value
+/// Returns a vector of frames representing individual pages alongside
+/// diagnostic information (errors and warnings).
pub fn typeset(
loader: &mut dyn Loader,
cache: &mut Cache,
- path: &Path,
+ path: Option<&Path>,
src: &str,
- base: &Scope,
+ scope: &Scope,
state: State,
) -> Pass<Vec<Frame>> {
let parsed = parse::parse(src);
- let evaluated = eval::eval(loader, cache, path, Rc::new(parsed.output), base);
+ let evaluated = eval::eval(loader, cache, path, Rc::new(parsed.output), scope);
let executed = exec::exec(&evaluated.output.template, state);
let layouted = layout::layout(loader, cache, &executed.output);
diff --git a/src/library/markup.rs b/src/library/markup.rs
index c218746b..0a80fe74 100644
--- a/src/library/markup.rs
+++ b/src/library/markup.rs
@@ -160,7 +160,7 @@ pub fn raw(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let snapshot = ctx.state.clone();
ctx.set_monospace();
- ctx.push_text(text.clone());
+ ctx.push_text(&text);
ctx.state = snapshot;
if block {
diff --git a/src/loading/fs.rs b/src/loading/fs.rs
index ac3f3706..7fa1c120 100644
--- a/src/loading/fs.rs
+++ b/src/loading/fs.rs
@@ -24,8 +24,7 @@ pub struct FsLoader {
cache: FileCache,
}
-/// Maps from paths to loaded file buffers. When the buffer is `None` the file
-/// does not exist or couldn't be read.
+/// Maps from resolved file hashes to loaded file buffers.
type FileCache = HashMap<FileHash, Buffer>;
impl FsLoader {
@@ -169,38 +168,29 @@ impl Loader for FsLoader {
}
fn load_face(&mut self, idx: usize) -> Option<Buffer> {
- load(&mut self.cache, &self.files[idx])
+ self.load_file(&self.files[idx].clone())
}
fn load_file(&mut self, path: &Path) -> Option<Buffer> {
- load(&mut self.cache, path)
+ let hash = self.resolve(path)?;
+ Some(Rc::clone(match self.cache.entry(hash) {
+ Entry::Occupied(entry) => entry.into_mut(),
+ Entry::Vacant(entry) => {
+ let buffer = std::fs::read(path).ok()?;
+ entry.insert(Rc::new(buffer))
+ }
+ }))
}
fn resolve(&self, path: &Path) -> Option<FileHash> {
- hash(path)
- }
-}
-
-/// Load from the file system using a cache.
-fn load(cache: &mut FileCache, path: &Path) -> Option<Buffer> {
- Some(match cache.entry(hash(path)?) {
- Entry::Occupied(entry) => entry.get().clone(),
- Entry::Vacant(entry) => {
- let buffer = std::fs::read(path).ok()?;
- entry.insert(Rc::new(buffer)).clone()
+ let file = File::open(path).ok()?;
+ let meta = file.metadata().ok()?;
+ if meta.is_file() {
+ let handle = Handle::from_file(file).ok()?;
+ Some(FileHash::from_raw(fxhash::hash64(&handle)))
+ } else {
+ None
}
- })
-}
-
-/// Create a hash that is the same for all paths pointing to the same file.
-fn hash(path: &Path) -> Option<FileHash> {
- let file = File::open(path).ok()?;
- let meta = file.metadata().ok()?;
- if meta.is_file() {
- let handle = Handle::from_file(file).ok()?;
- Some(FileHash(fxhash::hash64(&handle)))
- } else {
- None
}
}
diff --git a/src/loading/mod.rs b/src/loading/mod.rs
index f57b5c73..0e171796 100644
--- a/src/loading/mod.rs
+++ b/src/loading/mod.rs
@@ -36,7 +36,19 @@ pub trait Loader {
///
/// Should be the same for all paths pointing to the same file.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct FileHash(pub u64);
+pub struct FileHash(u64);
+
+impl FileHash {
+ /// Create an file hash from a raw hash value.
+ pub fn from_raw(v: u64) -> Self {
+ Self(v)
+ }
+
+ /// Convert into the raw underlying hash value.
+ pub fn into_raw(self) -> u64 {
+ self.0
+ }
+}
/// A loader which serves nothing.
pub struct BlankLoader;
diff --git a/src/main.rs b/src/main.rs
index 449cad20..d0762b3e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,8 +2,7 @@ use std::fs;
use std::path::{Path, PathBuf};
use anyhow::{anyhow, bail, Context};
-
-use typst::loading::Loader;
+use same_file::is_same_file;
fn main() -> anyhow::Result<()> {
let args: Vec<_> = std::env::args().collect();
@@ -33,9 +32,7 @@ fn main() -> anyhow::Result<()> {
};
// Ensure that the source file is not overwritten.
- let src_hash = loader.resolve(&src_path);
- let dest_hash = loader.resolve(&dest_path);
- if src_hash.is_some() && src_hash == dest_hash {
+ if is_same_file(src_path, &dest_path).unwrap_or(false) {
bail!("source and destination files are the same");
}
@@ -47,7 +44,14 @@ fn main() -> anyhow::Result<()> {
let mut cache = typst::cache::Cache::new(&loader);
let scope = typst::library::new();
let state = typst::exec::State::default();
- let pass = typst::typeset(&mut loader, &mut cache, &src_path, &src, &scope, state);
+ let pass = typst::typeset(
+ &mut loader,
+ &mut cache,
+ Some(&src_path),
+ &src,
+ &scope,
+ state,
+ );
// Print diagnostics.
let map = typst::parse::LineMap::new(&src);
diff --git a/src/util.rs b/src/util.rs
index 72db4518..8a8c04b6 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -2,6 +2,7 @@
use std::cmp::Ordering;
use std::ops::Range;
+use std::path::{Component, Path, PathBuf};
/// Additional methods for slices.
pub trait SliceExt<T> {
@@ -79,3 +80,28 @@ impl RangeExt for Range<usize> {
}
}
}
+
+/// Additional methods for [`Path`].
+pub trait PathExt {
+ /// Lexically normalize a path.
+ fn normalize(&self) -> PathBuf;
+}
+
+impl PathExt for Path {
+ fn normalize(&self) -> PathBuf {
+ let mut out = PathBuf::new();
+ for component in self.components() {
+ match component {
+ Component::CurDir => {}
+ Component::ParentDir => match out.components().next_back() {
+ Some(Component::Normal(_)) => {
+ out.pop();
+ }
+ _ => out.push(component),
+ },
+ _ => out.push(component),
+ }
+ }
+ out
+ }
+}