mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Fix and improve
- Set context location to resolved path during module evaluation. - Dump module diagnostics on import - Use same-file for more robustness than fs::canonicalize
This commit is contained in:
parent
e023bf2ac9
commit
00ac68b845
@ -7,7 +7,7 @@ edition = "2018"
|
|||||||
[features]
|
[features]
|
||||||
default = ["cli", "fs"]
|
default = ["cli", "fs"]
|
||||||
cli = ["anyhow", "fs"]
|
cli = ["anyhow", "fs"]
|
||||||
fs = ["dirs", "memmap2", "walkdir"]
|
fs = ["dirs", "memmap2", "same-file", "walkdir"]
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["bench"]
|
members = ["bench"]
|
||||||
@ -35,6 +35,7 @@ xi-unicode = "0.3"
|
|||||||
anyhow = { version = "1", optional = true }
|
anyhow = { version = "1", optional = true }
|
||||||
dirs = { version = "3", optional = true }
|
dirs = { version = "3", optional = true }
|
||||||
memmap2 = { version = "0.2", optional = true }
|
memmap2 = { version = "0.2", optional = true }
|
||||||
|
same-file = { version = "1", optional = true }
|
||||||
walkdir = { version = "2", optional = true }
|
walkdir = { version = "2", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
@ -11,6 +11,7 @@ pub use scope::*;
|
|||||||
pub use value::*;
|
pub use value::*;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::mem;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
@ -65,10 +66,10 @@ pub struct EvalContext<'a> {
|
|||||||
pub scopes: Scopes<'a>,
|
pub scopes: Scopes<'a>,
|
||||||
/// Evaluation diagnostics.
|
/// Evaluation diagnostics.
|
||||||
pub diags: DiagSet,
|
pub diags: DiagSet,
|
||||||
/// The stack of imported files that led to evaluation of the current file.
|
|
||||||
pub route: Vec<FileHash>,
|
|
||||||
/// The location of the currently evaluated file.
|
/// The location of the currently evaluated file.
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
/// The stack of imported files that led to evaluation of the current file.
|
||||||
|
pub route: Vec<FileHash>,
|
||||||
/// A map of loaded module.
|
/// A map of loaded module.
|
||||||
pub modules: HashMap<FileHash, Module>,
|
pub modules: HashMap<FileHash, Module>,
|
||||||
}
|
}
|
||||||
@ -91,8 +92,8 @@ impl<'a> EvalContext<'a> {
|
|||||||
cache,
|
cache,
|
||||||
scopes: Scopes::with_base(Some(base)),
|
scopes: Scopes::with_base(Some(base)),
|
||||||
diags: DiagSet::new(),
|
diags: DiagSet::new(),
|
||||||
route,
|
|
||||||
path: path.to_owned(),
|
path: path.to_owned(),
|
||||||
|
route,
|
||||||
modules: HashMap::new(),
|
modules: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,12 +117,13 @@ impl<'a> EvalContext<'a> {
|
|||||||
pub fn import(&mut self, path: &str, span: Span) -> Option<FileHash> {
|
pub fn import(&mut self, path: &str, span: Span) -> Option<FileHash> {
|
||||||
let (resolved, hash) = self.resolve(path, span)?;
|
let (resolved, hash) = self.resolve(path, span)?;
|
||||||
|
|
||||||
// Prevent cycling importing.
|
// Prevent cyclic importing.
|
||||||
if self.route.contains(&hash) {
|
if self.route.contains(&hash) {
|
||||||
self.diag(error!(span, "cyclic import"));
|
self.diag(error!(span, "cyclic import"));
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check whether the module was already loaded.
|
||||||
if self.modules.get(&hash).is_some() {
|
if self.modules.get(&hash).is_some() {
|
||||||
return Some(hash);
|
return Some(hash);
|
||||||
}
|
}
|
||||||
@ -136,19 +138,33 @@ impl<'a> EvalContext<'a> {
|
|||||||
None
|
None
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
// Parse the file.
|
||||||
|
let parsed = parse(string);
|
||||||
|
|
||||||
// Prepare the new context.
|
// Prepare the new context.
|
||||||
self.route.push(hash);
|
|
||||||
let new_scopes = Scopes::with_base(self.scopes.base);
|
let new_scopes = Scopes::with_base(self.scopes.base);
|
||||||
let old_scopes = std::mem::replace(&mut self.scopes, new_scopes);
|
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);
|
||||||
|
self.route.push(hash);
|
||||||
|
|
||||||
// Evaluate the module.
|
// Evaluate the module.
|
||||||
let tree = Rc::new(parse(string).output);
|
let tree = Rc::new(parsed.output);
|
||||||
let map = tree.eval(self);
|
let map = tree.eval(self);
|
||||||
|
|
||||||
// Restore the old context.
|
// Restore the old context.
|
||||||
let new_scopes = std::mem::replace(&mut self.scopes, old_scopes);
|
let new_scopes = mem::replace(&mut self.scopes, old_scopes);
|
||||||
|
let new_diags = mem::replace(&mut self.diags, old_diags);
|
||||||
|
self.path = old_path;
|
||||||
self.route.pop();
|
self.route.pop();
|
||||||
|
|
||||||
|
// Put all diagnostics from the module on the import.
|
||||||
|
for mut diag in new_diags {
|
||||||
|
diag.span = span;
|
||||||
|
self.diag(diag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the evaluated module.
|
||||||
self.modules.insert(hash, Module {
|
self.modules.insert(hash, Module {
|
||||||
scope: new_scopes.top,
|
scope: new_scopes.top,
|
||||||
template: vec![TemplateNode::Tree { tree, map }],
|
template: vec![TemplateNode::Tree { tree, map }],
|
||||||
@ -430,7 +446,7 @@ impl BinaryExpr {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let lhs = std::mem::take(&mut *mutable);
|
let lhs = mem::take(&mut *mutable);
|
||||||
let types = (lhs.type_name(), rhs.type_name());
|
let types = (lhs.type_name(), rhs.type_name());
|
||||||
*mutable = op(lhs, rhs);
|
*mutable = op(lhs, rhs);
|
||||||
|
|
||||||
@ -513,7 +529,7 @@ impl Eval for ClosureExpr {
|
|||||||
Value::Func(FuncValue::new(name, move |ctx, args| {
|
Value::Func(FuncValue::new(name, move |ctx, args| {
|
||||||
// Don't leak the scopes from the call site. Instead, we use the
|
// Don't leak the scopes from the call site. Instead, we use the
|
||||||
// scope of captured variables we collected earlier.
|
// scope of captured variables we collected earlier.
|
||||||
let prev = std::mem::take(&mut ctx.scopes);
|
let prev = mem::take(&mut ctx.scopes);
|
||||||
ctx.scopes.top = captured.clone();
|
ctx.scopes.top = captured.clone();
|
||||||
|
|
||||||
for param in params.iter() {
|
for param in params.iter() {
|
||||||
@ -657,11 +673,9 @@ impl Eval for ImportExpr {
|
|||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
|
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
|
||||||
let span = self.path.span();
|
|
||||||
let path = self.path.eval(ctx);
|
let path = self.path.eval(ctx);
|
||||||
|
if let Some(path) = ctx.cast::<String>(path, self.path.span()) {
|
||||||
if let Some(path) = ctx.cast::<String>(path, span) {
|
if let Some(hash) = ctx.import(&path, self.path.span()) {
|
||||||
if let Some(hash) = ctx.import(&path, span) {
|
|
||||||
let mut module = &ctx.modules[&hash];
|
let mut module = &ctx.modules[&hash];
|
||||||
match &self.imports {
|
match &self.imports {
|
||||||
Imports::Wildcard => {
|
Imports::Wildcard => {
|
||||||
@ -695,11 +709,9 @@ impl Eval for IncludeExpr {
|
|||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
|
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
|
||||||
let span = self.path.span();
|
|
||||||
let path = self.path.eval(ctx);
|
let path = self.path.eval(ctx);
|
||||||
|
if let Some(path) = ctx.cast::<String>(path, self.path.span()) {
|
||||||
if let Some(path) = ctx.cast::<String>(path, span) {
|
if let Some(hash) = ctx.import(&path, self.path.span()) {
|
||||||
if let Some(hash) = ctx.import(&path, span) {
|
|
||||||
return Value::Template(ctx.modules[&hash].template.clone());
|
return Value::Template(ctx.modules[&hash].template.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,20 +16,19 @@ use super::*;
|
|||||||
/// - `ltr`
|
/// - `ltr`
|
||||||
/// - `rtl`
|
/// - `rtl`
|
||||||
pub fn lang(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
pub fn lang(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||||
let iso = args.eat::<String>(ctx).map(|s| s.to_ascii_lowercase());
|
let iso = args.eat::<String>(ctx).map(|s| lang_dir(&s));
|
||||||
let dir = args.eat_named::<Spanned<Dir>>(ctx, "dir");
|
let dir = match args.eat_named::<Spanned<Dir>>(ctx, "dir") {
|
||||||
|
Some(dir) if dir.v.axis() == SpecAxis::Horizontal => Some(dir.v),
|
||||||
|
Some(dir) => {
|
||||||
|
ctx.diag(error!(dir.span, "must be horizontal"));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
Value::template("lang", move |ctx| {
|
Value::template("lang", move |ctx| {
|
||||||
if let Some(iso) = &iso {
|
if let Some(dir) = dir.or(iso) {
|
||||||
ctx.state.lang.dir = lang_dir(iso);
|
ctx.state.lang.dir = dir;
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(dir) = dir {
|
|
||||||
if dir.v.axis() == SpecAxis::Horizontal {
|
|
||||||
ctx.state.lang.dir = dir.v;
|
|
||||||
} else {
|
|
||||||
ctx.diag(error!(dir.span, "must be horizontal"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.parbreak();
|
ctx.parbreak();
|
||||||
@ -38,7 +37,7 @@ pub fn lang(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
|||||||
|
|
||||||
/// The default direction for the language identified by `iso`.
|
/// The default direction for the language identified by `iso`.
|
||||||
fn lang_dir(iso: &str) -> Dir {
|
fn lang_dir(iso: &str) -> Dir {
|
||||||
match iso {
|
match iso.to_ascii_lowercase().as_str() {
|
||||||
"ar" | "he" | "fa" | "ur" | "ps" | "yi" => Dir::RTL,
|
"ar" | "he" | "fa" | "ur" | "ps" | "yi" => Dir::RTL,
|
||||||
"en" | "fr" | "de" | _ => Dir::LTR,
|
"en" | "fr" | "de" | _ => Dir::LTR,
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ use std::path::{Path, PathBuf};
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use memmap2::Mmap;
|
use memmap2::Mmap;
|
||||||
|
use same_file::Handle;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ttf_parser::{name_id, Face};
|
use ttf_parser::{name_id, Face};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
@ -167,10 +168,6 @@ impl Loader for FsLoader {
|
|||||||
&self.faces
|
&self.faces
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve(&self, path: &Path) -> Option<FileHash> {
|
|
||||||
hash(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_face(&mut self, idx: usize) -> Option<Buffer> {
|
fn load_face(&mut self, idx: usize) -> Option<Buffer> {
|
||||||
load(&mut self.cache, &self.files[idx])
|
load(&mut self.cache, &self.files[idx])
|
||||||
}
|
}
|
||||||
@ -178,6 +175,10 @@ impl Loader for FsLoader {
|
|||||||
fn load_file(&mut self, path: &Path) -> Option<Buffer> {
|
fn load_file(&mut self, path: &Path) -> Option<Buffer> {
|
||||||
load(&mut self.cache, path)
|
load(&mut self.cache, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolve(&self, path: &Path) -> Option<FileHash> {
|
||||||
|
hash(path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load from the file system using a cache.
|
/// Load from the file system using a cache.
|
||||||
@ -191,8 +192,11 @@ fn load(cache: &mut FileCache, path: &Path) -> Option<Buffer> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a hash that is the same for all paths pointing to the same file.
|
||||||
fn hash(path: &Path) -> Option<FileHash> {
|
fn hash(path: &Path) -> Option<FileHash> {
|
||||||
path.canonicalize().ok().map(|p| FileHash(fxhash::hash64(&p)))
|
Handle::from_path(path)
|
||||||
|
.map(|handle| FileHash(fxhash::hash64(&handle)))
|
||||||
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -19,19 +19,22 @@ pub trait Loader {
|
|||||||
/// Descriptions of all font faces this loader serves.
|
/// Descriptions of all font faces this loader serves.
|
||||||
fn faces(&self) -> &[FaceInfo];
|
fn faces(&self) -> &[FaceInfo];
|
||||||
|
|
||||||
/// Resolve a hash that is the same for all paths pointing to the same file.
|
|
||||||
///
|
|
||||||
/// Should return `None` if the file does not exist.
|
|
||||||
fn resolve(&self, path: &Path) -> Option<FileHash>;
|
|
||||||
|
|
||||||
/// Load the font face with the given index in [`faces()`](Self::faces).
|
/// Load the font face with the given index in [`faces()`](Self::faces).
|
||||||
fn load_face(&mut self, idx: usize) -> Option<Buffer>;
|
fn load_face(&mut self, idx: usize) -> Option<Buffer>;
|
||||||
|
|
||||||
/// Load a file from a path.
|
/// Load a file from a path.
|
||||||
fn load_file(&mut self, path: &Path) -> Option<Buffer>;
|
fn load_file(&mut self, path: &Path) -> Option<Buffer>;
|
||||||
|
|
||||||
|
/// Resolve a hash for the file the path points to.
|
||||||
|
///
|
||||||
|
/// This should return the same hash for all paths pointing to the same file
|
||||||
|
/// and `None` if the file does not exist.
|
||||||
|
fn resolve(&self, path: &Path) -> Option<FileHash>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A hash that must be the same for all paths pointing to the same file.
|
/// A file hash that can be [resolved](Loader::resolve) from a path.
|
||||||
|
///
|
||||||
|
/// Should be the same for all paths pointing to the same file.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct FileHash(pub u64);
|
pub struct FileHash(pub u64);
|
||||||
|
|
||||||
@ -43,10 +46,6 @@ impl Loader for BlankLoader {
|
|||||||
&[]
|
&[]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve(&self, _: &Path) -> Option<FileHash> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_face(&mut self, _: usize) -> Option<Buffer> {
|
fn load_face(&mut self, _: usize) -> Option<Buffer> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -54,4 +53,8 @@ impl Loader for BlankLoader {
|
|||||||
fn load_file(&mut self, _: &Path) -> Option<Buffer> {
|
fn load_file(&mut self, _: &Path) -> Option<Buffer> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolve(&self, _: &Path) -> Option<FileHash> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user