summaryrefslogtreecommitdiff
path: root/src/model
diff options
context:
space:
mode:
Diffstat (limited to 'src/model')
-rw-r--r--src/model/eval.rs60
-rw-r--r--src/model/func.rs8
-rw-r--r--src/model/mod.rs2
-rw-r--r--src/model/module.rs62
-rw-r--r--src/model/value.rs9
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)]