diff options
| author | Laurenz <laurmaedje@gmail.com> | 2024-10-27 19:04:55 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-10-27 18:04:55 +0000 |
| commit | be7cfc85d08c545abfac08098b7b33b4bd71f37e (patch) | |
| tree | f4137fa2aaa57babae1f7603a9b2ed7e688f43d8 /crates/typst-eval/src/import.rs | |
| parent | b8034a343831e8609aec2ec81eb7eeda57aa5d81 (diff) | |
Split out four new crates (#5302)
Diffstat (limited to 'crates/typst-eval/src/import.rs')
| -rw-r--r-- | crates/typst-eval/src/import.rs | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/crates/typst-eval/src/import.rs b/crates/typst-eval/src/import.rs new file mode 100644 index 00000000..316fbf87 --- /dev/null +++ b/crates/typst-eval/src/import.rs @@ -0,0 +1,230 @@ +use comemo::TrackedMut; +use ecow::{eco_format, eco_vec, EcoString}; +use typst_library::diag::{ + bail, error, warning, At, FileError, SourceResult, Trace, Tracepoint, +}; +use typst_library::foundations::{Content, Module, Value}; +use typst_library::World; +use typst_syntax::ast::{self, AstNode}; +use typst_syntax::package::{PackageManifest, PackageSpec}; +use typst_syntax::{FileId, Span, VirtualPath}; + +use crate::{eval, Eval, Vm}; + +impl Eval for ast::ModuleImport<'_> { + type Output = Value; + + fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> { + let source = self.source(); + let source_span = source.span(); + let mut source = source.eval(vm)?; + let new_name = self.new_name(); + let imports = self.imports(); + + match &source { + Value::Func(func) => { + if func.scope().is_none() { + bail!(source_span, "cannot import from user-defined functions"); + } + } + Value::Type(_) => {} + other => { + source = Value::Module(import(vm, other.clone(), source_span, true)?); + } + } + + if let Some(new_name) = new_name { + if let ast::Expr::Ident(ident) = self.source() { + if ident.as_str() == new_name.as_str() { + // Warn on `import x as x` + vm.engine.sink.warn(warning!( + new_name.span(), + "unnecessary import rename to same name", + )); + } + } + + // Define renamed module on the scope. + vm.scopes.top.define_ident(new_name, source.clone()); + } + + let scope = source.scope().unwrap(); + match imports { + None => { + // Only import here if there is no rename. + if new_name.is_none() { + let name: EcoString = source.name().unwrap().into(); + vm.scopes.top.define(name, source); + } + } + Some(ast::Imports::Wildcard) => { + for (var, value, span) in scope.iter() { + vm.scopes.top.define_spanned(var.clone(), value.clone(), span); + } + } + Some(ast::Imports::Items(items)) => { + let mut errors = eco_vec![]; + for item in items.iter() { + let mut path = item.path().iter().peekable(); + let mut scope = scope; + + while let Some(component) = &path.next() { + let Some(value) = scope.get(component) else { + errors.push(error!(component.span(), "unresolved import")); + break; + }; + + if path.peek().is_some() { + // Nested import, as this is not the last component. + // This must be a submodule. + let Some(submodule) = value.scope() else { + let error = if matches!(value, Value::Func(function) if function.scope().is_none()) + { + error!( + component.span(), + "cannot import from user-defined functions" + ) + } else if !matches!( + value, + Value::Func(_) | Value::Module(_) | Value::Type(_) + ) { + error!( + component.span(), + "expected module, function, or type, found {}", + value.ty() + ) + } else { + panic!("unexpected nested import failure") + }; + errors.push(error); + break; + }; + + // Walk into the submodule. + scope = submodule; + } else { + // Now that we have the scope of the innermost submodule + // in the import path, we may extract the desired item from + // it. + + // Warn on `import ...: x as x` + if let ast::ImportItem::Renamed(renamed_item) = &item { + if renamed_item.original_name().as_str() + == renamed_item.new_name().as_str() + { + vm.engine.sink.warn(warning!( + renamed_item.new_name().span(), + "unnecessary import rename to same name", + )); + } + } + + vm.define(item.bound_name(), value.clone()); + } + } + } + if !errors.is_empty() { + return Err(errors); + } + } + } + + Ok(Value::None) + } +} + +impl Eval for ast::ModuleInclude<'_> { + type Output = Content; + + fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> { + let span = self.source().span(); + let source = self.source().eval(vm)?; + let module = import(vm, source, span, false)?; + Ok(module.content()) + } +} + +/// Process an import of a module relative to the current location. +pub fn import( + vm: &mut Vm, + source: Value, + span: Span, + allow_scopes: bool, +) -> SourceResult<Module> { + let path = match source { + Value::Str(path) => path, + Value::Module(module) => return Ok(module), + v if allow_scopes => { + bail!(span, "expected path, module, function, or type, found {}", v.ty()) + } + v => bail!(span, "expected path or module, found {}", v.ty()), + }; + + // Handle package and file imports. + let path = path.as_str(); + if path.starts_with('@') { + let spec = path.parse::<PackageSpec>().at(span)?; + import_package(vm, spec, span) + } else { + import_file(vm, path, span) + } +} + +/// Import an external package. +fn import_package(vm: &mut Vm, spec: PackageSpec, span: Span) -> SourceResult<Module> { + // Evaluate the manifest. + let world = vm.world(); + let manifest_id = FileId::new(Some(spec.clone()), VirtualPath::new("typst.toml")); + let bytes = world.file(manifest_id).at(span)?; + let string = std::str::from_utf8(&bytes).map_err(FileError::from).at(span)?; + let manifest: PackageManifest = toml::from_str(string) + .map_err(|err| eco_format!("package manifest is malformed ({})", err.message())) + .at(span)?; + manifest.validate(&spec).at(span)?; + + // Evaluate the entry point. + let entrypoint_id = manifest_id.join(&manifest.package.entrypoint); + let source = world.source(entrypoint_id).at(span)?; + + // Prevent cyclic importing. + if vm.engine.route.contains(source.id()) { + bail!(span, "cyclic import"); + } + + let point = || Tracepoint::Import; + Ok(eval( + vm.engine.routines, + vm.engine.world, + vm.engine.traced, + TrackedMut::reborrow_mut(&mut vm.engine.sink), + vm.engine.route.track(), + &source, + ) + .trace(world, point, span)? + .with_name(manifest.package.name)) +} + +/// Import a file from a path. +fn import_file(vm: &mut Vm, path: &str, span: Span) -> SourceResult<Module> { + // Load the source file. + let world = vm.world(); + let id = span.resolve_path(path).at(span)?; + let source = world.source(id).at(span)?; + + // Prevent cyclic importing. + if vm.engine.route.contains(source.id()) { + bail!(span, "cyclic import"); + } + + // Evaluate the file. + let point = || Tracepoint::Import; + eval( + vm.engine.routines, + vm.engine.world, + vm.engine.traced, + TrackedMut::reborrow_mut(&mut vm.engine.sink), + vm.engine.route.track(), + &source, + ) + .trace(world, point, span) +} |
