summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/eval/func.rs21
-rw-r--r--src/eval/mod.rs8
-rw-r--r--src/image.rs3
-rw-r--r--src/lib.rs44
-rw-r--r--src/library/graphics/image.rs12
-rw-r--r--src/main.rs19
-rw-r--r--src/source.rs3
7 files changed, 77 insertions, 33 deletions
diff --git a/src/eval/func.rs b/src/eval/func.rs
index 4c5761ab..f15b0241 100644
--- a/src/eval/func.rs
+++ b/src/eval/func.rs
@@ -5,6 +5,7 @@ use std::sync::Arc;
use super::{Args, Eval, Flow, Scope, Scopes, Value};
use crate::diag::{StrResult, TypResult};
use crate::model::{Content, NodeId, StyleMap};
+use crate::source::SourceId;
use crate::syntax::ast::Expr;
use crate::util::EcoString;
use crate::Context;
@@ -174,6 +175,8 @@ pub trait Node: 'static {
/// A user-defined closure.
#[derive(Hash)]
pub struct Closure {
+ /// The location where the closure was defined.
+ pub location: Option<SourceId>,
/// The name of the closure.
pub name: Option<EcoString>,
/// Captured values from outer scopes.
@@ -212,18 +215,28 @@ impl Closure {
// Backup the old control flow state.
let prev_flow = ctx.flow.take();
+ let detached = ctx.route.is_empty();
+ if detached {
+ ctx.route = self.location.into_iter().collect();
+ }
// Evaluate the body.
- let mut value = self.body.eval(ctx, &mut scp)?;
+ let result = self.body.eval(ctx, &mut scp);
+
+ // Restore the old control flow state.
+ let flow = std::mem::replace(&mut ctx.flow, prev_flow);
+ if detached {
+ ctx.route.clear();
+ }
// Handle control flow.
- match std::mem::replace(&mut ctx.flow, prev_flow) {
- Some(Flow::Return(_, Some(explicit))) => value = explicit,
+ match flow {
+ Some(Flow::Return(_, Some(explicit))) => return Ok(explicit),
Some(Flow::Return(_, None)) => {}
Some(flow) => return Err(flow.forbidden())?,
None => {}
}
- Ok(value)
+ result
}
}
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index b35cf1ef..79060137 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -707,6 +707,7 @@ impl Eval for ClosureExpr {
// Define the actual function.
Ok(Value::Func(Func::from_closure(Closure {
+ location: ctx.route.last().copied(),
name,
captured,
params,
@@ -765,6 +766,7 @@ impl Eval for ShowExpr {
let body = self.body();
let span = body.span();
let func = Func::from_closure(Closure {
+ location: ctx.route.last().copied(),
name: None,
captured,
params,
@@ -945,9 +947,11 @@ impl Eval for IncludeExpr {
/// Process an import of a module relative to the current location.
fn import(ctx: &mut Context, path: &str, span: Span) -> TypResult<Module> {
// Load the source file.
- let full = ctx.complete_path(path);
+ let full = ctx.locate(&path).at(span)?;
let id = ctx.sources.load(&full).map_err(|err| match err.kind() {
- std::io::ErrorKind::NotFound => error!(span, "file not found"),
+ std::io::ErrorKind::NotFound => {
+ error!(span, "file not found (searched at {})", full.display())
+ }
_ => error!(span, "failed to load source file ({})", err),
})?;
diff --git a/src/image.rs b/src/image.rs
index 24a0deec..87c093d3 100644
--- a/src/image.rs
+++ b/src/image.rs
@@ -48,7 +48,8 @@ impl ImageStore {
}
}
- /// Load and decode an image file from a path.
+ /// Load and decode an image file from a path relative to the compilation
+ /// environment's root.
pub fn load(&mut self, path: &Path) -> io::Result<ImageId> {
let hash = self.loader.resolve(path)?;
Ok(*match self.files.entry(hash) {
diff --git a/src/lib.rs b/src/lib.rs
index efddc239..eb6e8f72 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -57,7 +57,7 @@ use std::hash::Hash;
use std::path::PathBuf;
use std::sync::Arc;
-use crate::diag::TypResult;
+use crate::diag::{StrResult, TypResult};
use crate::eval::{Eval, Flow, Module, Scope, Scopes};
use crate::font::FontStore;
use crate::frame::Frame;
@@ -65,6 +65,7 @@ use crate::image::ImageStore;
use crate::loading::Loader;
use crate::model::StyleMap;
use crate::source::{SourceId, SourceStore};
+use crate::util::PathExt;
/// The core context which holds the loader, configuration and cached artifacts.
pub struct Context {
@@ -76,6 +77,8 @@ pub struct Context {
pub fonts: FontStore,
/// Stores decoded images.
pub images: ImageStore,
+ /// The compilation root.
+ root: PathBuf,
/// The standard library scope.
std: Arc<Scope>,
/// The default styles.
@@ -172,51 +175,64 @@ impl Context {
self.evaluate(id)?.content.layout(self)
}
- /// Resolve a user-entered path (relative to the current evaluation
- /// location) to be relative to the compilation environment's root.
- pub fn complete_path(&self, path: &str) -> PathBuf {
+ /// Resolve a user-entered path to be relative to the compilation
+ /// environment's root.
+ pub fn locate(&self, path: &str) -> StrResult<PathBuf> {
if let Some(&id) = self.route.last() {
+ if let Some(path) = path.strip_prefix('/') {
+ return Ok(self.root.join(path).normalize());
+ }
+
if let Some(dir) = self.sources.get(id).path().parent() {
- return dir.join(path);
+ return Ok(dir.join(path).normalize());
}
}
- path.into()
+ return Err("cannot access file system from here".into());
}
}
/// A builder for a [`Context`].
///
/// This struct is created by [`Context::builder`].
+#[derive(Default)]
pub struct ContextBuilder {
+ root: PathBuf,
std: Option<Arc<Scope>>,
styles: Option<Arc<StyleMap>>,
}
impl ContextBuilder {
+ /// The compilation root, relative to which absolute paths are.
+ pub fn root(&mut self, root: impl Into<PathBuf>) -> &mut Self {
+ self.root = root.into();
+ self
+ }
+
/// The scope containing definitions that are available everywhere
/// (the standard library).
- pub fn std(mut self, std: impl Into<Arc<Scope>>) -> Self {
+ pub fn std(&mut self, std: impl Into<Arc<Scope>>) -> &mut Self {
self.std = Some(std.into());
self
}
/// The default properties for page size, font selection and so on.
- pub fn styles(mut self, styles: impl Into<Arc<StyleMap>>) -> Self {
+ pub fn styles(&mut self, styles: impl Into<Arc<StyleMap>>) -> &mut Self {
self.styles = Some(styles.into());
self
}
/// Finish building the context by providing the `loader` used to load
/// fonts, images, source files and other resources.
- pub fn build(self, loader: Arc<dyn Loader>) -> Context {
+ pub fn build(&self, loader: Arc<dyn Loader>) -> Context {
Context {
sources: SourceStore::new(Arc::clone(&loader)),
fonts: FontStore::new(Arc::clone(&loader)),
images: ImageStore::new(Arc::clone(&loader)),
loader,
- std: self.std.unwrap_or_else(|| Arc::new(library::new())),
- styles: self.styles.unwrap_or_default(),
+ root: self.root.clone(),
+ std: self.std.clone().unwrap_or_else(|| Arc::new(library::new())),
+ styles: self.styles.clone().unwrap_or_default(),
modules: HashMap::new(),
cache: HashMap::new(),
route: vec![],
@@ -226,12 +242,6 @@ impl ContextBuilder {
}
}
-impl Default for ContextBuilder {
- fn default() -> Self {
- Self { std: None, styles: None }
- }
-}
-
/// An entry in the query cache.
struct CacheEntry {
/// The query's results.
diff --git a/src/library/graphics/image.rs b/src/library/graphics/image.rs
index ee854130..6fd465cb 100644
--- a/src/library/graphics/image.rs
+++ b/src/library/graphics/image.rs
@@ -12,11 +12,15 @@ impl ImageNode {
pub const FIT: ImageFit = ImageFit::Cover;
fn construct(ctx: &mut Context, args: &mut Args) -> TypResult<Content> {
- let path = args.expect::<Spanned<EcoString>>("path to image file")?;
- let full = ctx.complete_path(&path.v);
+ let Spanned { v: path, span } =
+ args.expect::<Spanned<EcoString>>("path to image file")?;
+
+ let full = ctx.locate(&path).at(span)?;
let id = ctx.images.load(&full).map_err(|err| match err.kind() {
- std::io::ErrorKind::NotFound => error!(path.span, "file not found"),
- _ => error!(path.span, "failed to load image ({})", err),
+ std::io::ErrorKind::NotFound => {
+ error!(span, "file not found (searched at {})", full.display())
+ }
+ _ => error!(span, "failed to load image ({})", err),
})?;
let width = args.named("width")?;
diff --git a/src/main.rs b/src/main.rs
index daeff033..59ad5a71 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -22,9 +22,10 @@ USAGE:
OPTIONS:
-h, --help Print this help
+ --root <dir> Configure the root for absolute paths
ARGS:
- <input.typ> Path input Typst file
+ <input.typ> Path to input Typst file
[output.pdf] Path to output PDF
";
@@ -44,9 +45,17 @@ fn main() {
fn try_main(args: Args) -> Result<(), String> {
// Create a loader for fonts and files.
let mut loader = FsLoader::new();
+ let mut builder = Context::builder();
+ if let Some(root) = &args.root {
+ builder.root(root);
+ }
// Search for fonts in the project directory.
if let Some(dir) = args.input.parent() {
+ if args.root.is_none() {
+ builder.root(dir);
+ }
+
if dir.as_os_str().is_empty() {
// Just a filename, so directory is current directory.
loader.search_path(".");
@@ -60,7 +69,7 @@ fn try_main(args: Args) -> Result<(), String> {
// Create the context which holds loaded source files, fonts, images and
// cached artifacts.
- let mut ctx = Context::new(loader.wrap());
+ let mut ctx = builder.build(loader.wrap());
// Ensure that the source file is not overwritten.
if is_same_file(&args.input, &args.output).unwrap_or(false) {
@@ -94,6 +103,7 @@ fn try_main(args: Args) -> Result<(), String> {
struct Args {
input: PathBuf,
output: PathBuf,
+ root: Option<PathBuf>,
}
/// Parse command line arguments.
@@ -104,7 +114,8 @@ fn parse_args() -> Result<Args, String> {
std::process::exit(0);
}
- let input = args.free_from_str::<PathBuf>().map_err(|_| "missing input file")?;
+ let root = args.opt_value_from_str("--root").map_err(|_| "malformed root")?;
+ let input: PathBuf = args.free_from_str().map_err(|_| "missing input file")?;
let output = match args.opt_free_from_str().ok().flatten() {
Some(output) => output,
None => {
@@ -118,7 +129,7 @@ fn parse_args() -> Result<Args, String> {
Err("too many arguments")?;
}
- Ok(Args { input, output })
+ Ok(Args { input, output, root })
}
/// Print an application-level error (independent from a source file).
diff --git a/src/source.rs b/src/source.rs
index a7c95255..780e12a8 100644
--- a/src/source.rs
+++ b/src/source.rs
@@ -54,7 +54,8 @@ impl SourceStore {
}
}
- /// Load a source file from a path using the `loader`.
+ /// Load a source file from a path relative to the compilation environment's
+ /// root.
///
/// If there already exists a source file for this path, it is
/// [replaced](SourceFile::replace).