diff options
Diffstat (limited to 'src/model')
| -rw-r--r-- | src/model/eval.rs | 60 | ||||
| -rw-r--r-- | src/model/func.rs | 8 | ||||
| -rw-r--r-- | src/model/mod.rs | 2 | ||||
| -rw-r--r-- | src/model/module.rs | 62 | ||||
| -rw-r--r-- | src/model/value.rs | 9 |
5 files changed, 110 insertions, 31 deletions
diff --git a/src/model/eval.rs b/src/model/eval.rs index 959e4166..70302861 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -9,7 +9,7 @@ use unicode_segmentation::UnicodeSegmentation; use super::{ methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Func, Label, - LangItems, Recipe, Scope, Scopes, Selector, StyleMap, Transform, Value, + LangItems, Module, Recipe, Scopes, Selector, StyleMap, Transform, Value, }; use crate::diag::{ bail, error, At, SourceError, SourceResult, StrResult, Trace, Tracepoint, @@ -17,7 +17,7 @@ use crate::diag::{ use crate::geom::{Abs, Angle, Em, Fr, Ratio}; use crate::syntax::ast::AstNode; use crate::syntax::{ast, Source, SourceId, Span, Spanned, SyntaxKind, SyntaxNode, Unit}; -use crate::util::{EcoString, PathExt}; +use crate::util::PathExt; use crate::World; const MAX_ITERATIONS: usize = 10_000; @@ -53,7 +53,7 @@ pub fn eval( } // Assemble the module. - Ok(Module { scope: vm.scopes.top, content: result? }) + Ok(Module::evaluated(source.path(), vm.scopes.top, result?)) } /// A virtual machine. @@ -181,15 +181,6 @@ impl Route { } } -/// An evaluated module, ready for importing or typesetting. -#[derive(Debug, Clone)] -pub struct Module { - /// The top-level definitions that were bound in this module. - pub scope: Scope, - /// The module's layoutable contents. - pub content: Content, -} - /// Evaluate an expression. pub(super) trait Eval { /// The output of evaluating the expression. @@ -803,7 +794,15 @@ impl Eval for ast::FieldAccess { Value::Dict(dict) => dict.at(&field).at(span)?.clone(), Value::Content(content) => content .field(&field) - .ok_or_else(|| format!("unknown field {field:?}")) + .ok_or_else(|| format!("unknown field `{field}`")) + .at(span)?, + Value::Module(module) => module + .scope() + .get(&field) + .cloned() + .ok_or_else(|| { + format!("module `{}` does not contain `{field}`", module.name()) + }) .at(span)?, v => bail!( self.target().span(), @@ -1163,19 +1162,22 @@ impl Eval for ast::ModuleImport { type Output = Value; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let span = self.path().span(); - let path = self.path().eval(vm)?.cast::<EcoString>().at(span)?; - let module = import(vm, &path, span)?; + let span = self.source().span(); + let source = self.source().eval(vm)?; + let module = import(vm, source, span)?; match self.imports() { - ast::Imports::Wildcard => { - for (var, value) in module.scope.iter() { + None => { + vm.scopes.top.define(module.name().clone(), module); + } + Some(ast::Imports::Wildcard) => { + for (var, value) in module.scope().iter() { vm.scopes.top.define(var.clone(), value.clone()); } } - ast::Imports::Items(idents) => { + Some(ast::Imports::Items(idents)) => { for ident in idents { - if let Some(value) = module.scope.get(&ident) { + if let Some(value) = module.scope().get(&ident) { vm.scopes.top.define(ident.take(), value.clone()); } else { bail!(ident.span(), "unresolved import"); @@ -1192,17 +1194,23 @@ impl Eval for ast::ModuleInclude { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let span = self.path().span(); - let path = self.path().eval(vm)?.cast::<EcoString>().at(span)?; - let module = import(vm, &path, span)?; - Ok(module.content) + let span = self.source().span(); + let source = self.source().eval(vm)?; + let module = import(vm, source, span)?; + Ok(module.content()) } } /// Process an import of a module relative to the current location. -fn import(vm: &Vm, path: &str, span: Span) -> SourceResult<Module> { +fn import(vm: &Vm, source: Value, span: Span) -> SourceResult<Module> { + let path = match source { + Value::Str(path) => path, + Value::Module(module) => return Ok(module), + v => bail!(span, "expected path or module, found {}", v.type_name()), + }; + // Load the source file. - let full = vm.locate(path).at(span)?; + let full = vm.locate(&path).at(span)?; let id = vm.world.resolve(&full).at(span)?; // Prevent cyclic importing. diff --git a/src/model/func.rs b/src/model/func.rs index 878b717f..98dc527c 100644 --- a/src/model/func.rs +++ b/src/model/func.rs @@ -443,8 +443,8 @@ impl<'a> CapturesVisitor<'a> { // An import contains items, but these are active only after the // path is evaluated. Some(ast::Expr::Import(expr)) => { - self.visit(expr.path().as_untyped()); - if let ast::Imports::Items(items) = expr.imports() { + self.visit(expr.source().as_untyped()); + if let Some(ast::Imports::Items(items)) = expr.imports() { for item in items { self.bind(item); } @@ -525,8 +525,8 @@ mod tests { test("#for x in y {} #x", &["x", "y"]); // Import. - test("#import x, y from z", &["z"]); - test("#import x, y, z from x + y", &["x", "y"]); + test("#import z: x, y", &["z"]); + test("#import x + y: x, y, z", &["x", "y"]); // Blocks. test("{ let x = 1; { let y = 2; y }; x + y }", &["y"]); diff --git a/src/model/mod.rs b/src/model/mod.rs index 6ba8014c..d84fe464 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -19,6 +19,7 @@ mod content; mod eval; mod func; mod methods; +mod module; mod ops; mod realize; mod scope; @@ -36,6 +37,7 @@ pub use self::dict::*; pub use self::eval::*; pub use self::func::*; pub use self::library::*; +pub use self::module::*; pub use self::realize::*; pub use self::scope::*; pub use self::str::*; diff --git a/src/model/module.rs b/src/model/module.rs new file mode 100644 index 00000000..c7cb7faf --- /dev/null +++ b/src/model/module.rs @@ -0,0 +1,62 @@ +use std::fmt::{self, Debug, Formatter}; +use std::path::Path; +use std::sync::Arc; + +use super::{Content, Scope}; +use crate::util::EcoString; + +/// An evaluated module, ready for importing or typesetting. +#[derive(Clone, Hash)] +pub struct Module(Arc<Repr>); + +/// The internal representation. +#[derive(Clone, Hash)] +struct Repr { + /// The module's name. + name: EcoString, + /// The top-level definitions that were bound in this module. + scope: Scope, + /// The module's layoutable contents. + content: Content, +} + +impl Module { + /// Create a new, empty module with the given `name`. + pub fn new(name: impl Into<EcoString>) -> Self { + Self(Arc::new(Repr { + name: name.into(), + scope: Scope::new(), + content: Content::empty(), + })) + } + + /// Create a new module from an evalauted file. + pub fn evaluated(path: &Path, scope: Scope, content: Content) -> Self { + let name = path.file_stem().unwrap_or_default().to_string_lossy().into(); + Self(Arc::new(Repr { name, scope, content })) + } + + /// Get the module's name. + pub fn name(&self) -> &EcoString { + &self.0.name + } + + /// Access the module's scope. + pub fn scope(&self) -> &Scope { + &self.0.scope + } + + /// Extract the module's content. + pub fn content(self) -> Content { + match Arc::try_unwrap(self.0) { + Ok(repr) => repr.content, + Err(arc) => arc.content.clone(), + } + } +} + +impl Debug for Module { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "<module {}>", self.name()) + } +} diff --git a/src/model/value.rs b/src/model/value.rs index 0716985d..8103b211 100644 --- a/src/model/value.rs +++ b/src/model/value.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use siphasher::sip128::{Hasher128, SipHasher}; use super::{ - format_str, ops, Args, Array, Cast, CastInfo, Content, Dict, Func, Label, Str, + format_str, ops, Args, Array, Cast, CastInfo, Content, Dict, Func, Label, Module, Str, }; use crate::diag::StrResult; use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel, RgbaColor}; @@ -52,6 +52,8 @@ pub enum Value { Func(Func), /// Captured arguments to a function. Args(Args), + /// A module. + Module(Module), /// A dynamic value. Dyn(Dynamic), } @@ -86,6 +88,7 @@ impl Value { Self::Dict(_) => Dict::TYPE_NAME, Self::Func(_) => Func::TYPE_NAME, Self::Args(_) => Args::TYPE_NAME, + Self::Module(_) => Module::TYPE_NAME, Self::Dyn(v) => v.type_name(), } } @@ -109,6 +112,7 @@ impl Value { Self::Str(v) => item!(text)(v.into()), Self::Content(v) => v, Self::Func(_) => Content::empty(), + Self::Module(module) => module.content(), _ => item!(raw)(self.repr().into(), Some("typc".into()), false), } } @@ -150,6 +154,7 @@ impl Debug for Value { Self::Dict(v) => Debug::fmt(v, f), Self::Func(v) => Debug::fmt(v, f), Self::Args(v) => Debug::fmt(v, f), + Self::Module(v) => Debug::fmt(v, f), Self::Dyn(v) => Debug::fmt(v, f), } } @@ -189,6 +194,7 @@ impl Hash for Value { Self::Dict(v) => v.hash(state), Self::Func(v) => v.hash(state), Self::Args(v) => v.hash(state), + Self::Module(v) => v.hash(state), Self::Dyn(v) => v.hash(state), } } @@ -402,6 +408,7 @@ primitive! { Content: "content", primitive! { Array: "array", Array } primitive! { Dict: "dictionary", Dict } primitive! { Func: "function", Func } +primitive! { Module: "module", Module } primitive! { Args: "arguments", Args } #[cfg(test)] |
