summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-01-03 12:29:35 +0100
committerLaurenz <laurmaedje@gmail.com>2023-01-03 12:32:17 +0100
commit29b31c4a5ac4cde311c4d38b3d70130e7d27ba76 (patch)
treefe4e5dbd2166a69af90e69578ad4602725cdb63c
parent54962e6dcd002fd27918827996155fd7dc4e1cff (diff)
New import syntax
-rw-r--r--library/src/compute/foundations.rs2
-rw-r--r--src/ide/highlight.rs13
-rw-r--r--src/lib.rs2
-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
-rw-r--r--src/syntax/ast.rs35
-rw-r--r--src/syntax/kind.rs8
-rw-r--r--src/syntax/parser.rs8
-rw-r--r--src/syntax/parsing.rs33
-rw-r--r--src/syntax/tokens.rs2
-rw-r--r--tests/ref/compiler/import.pngbin4538 -> 4091 bytes
-rw-r--r--tests/src/benches.rs3
-rw-r--r--tests/src/tests.rs2
-rw-r--r--tests/typ/compiler/block.typ2
-rw-r--r--tests/typ/compiler/closure.typ2
-rw-r--r--tests/typ/compiler/field.typ2
-rw-r--r--tests/typ/compiler/import.typ120
-rw-r--r--tests/typ/compiler/modules/cycle1.typ2
-rw-r--r--tests/typ/compiler/modules/cycle2.typ2
-rw-r--r--tests/typ/compiler/show-node.typ2
23 files changed, 232 insertions, 149 deletions
diff --git a/library/src/compute/foundations.rs b/library/src/compute/foundations.rs
index 4e8b9d82..162ba8c4 100644
--- a/library/src/compute/foundations.rs
+++ b/library/src/compute/foundations.rs
@@ -113,5 +113,5 @@ pub fn eval(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
let source = Source::synthesized(text, span);
let route = model::Route::default();
let module = model::eval(vm.world(), route.track(), &source)?;
- Ok(Value::Content(module.content))
+ Ok(Value::Content(module.content()))
}
diff --git a/src/ide/highlight.rs b/src/ide/highlight.rs
index 1f52ae95..321bf9a6 100644
--- a/src/ide/highlight.rs
+++ b/src/ide/highlight.rs
@@ -156,7 +156,7 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
SyntaxKind::Return => Some(Category::Keyword),
SyntaxKind::Import => Some(Category::Keyword),
SyntaxKind::Include => Some(Category::Keyword),
- SyntaxKind::From => Some(Category::Keyword),
+ SyntaxKind::As => Some(Category::Keyword),
SyntaxKind::Markup { .. }
if node.parent_kind() == Some(&SyntaxKind::TermItem)
@@ -198,6 +198,17 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
| SyntaxKind::Frac,
) => Some(Category::Interpolated),
Some(SyntaxKind::FuncCall) => Some(Category::Function),
+ Some(SyntaxKind::FieldAccess)
+ if node
+ .parent()
+ .and_then(|p| p.parent())
+ .filter(|gp| gp.kind() == &SyntaxKind::Parenthesized)
+ .and_then(|gp| gp.parent())
+ .map_or(false, |ggp| ggp.kind() == &SyntaxKind::FuncCall)
+ && node.next_sibling().is_none() =>
+ {
+ Some(Category::Function)
+ }
Some(SyntaxKind::MethodCall) if node.prev_sibling().is_some() => {
Some(Category::Function)
}
diff --git a/src/lib.rs b/src/lib.rs
index 4045c02d..e6410d84 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -66,7 +66,7 @@ pub fn compile(world: &(dyn World + 'static), source: &Source) -> SourceResult<D
let module = model::eval(world.track(), route.track(), source)?;
// Typeset the module's contents.
- model::typeset(world.track(), &module.content)
+ model::typeset(world.track(), &module.content())
}
/// The environment in which typesetting occurs.
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)]
diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs
index ccad77c2..4ef0b2a6 100644
--- a/src/syntax/ast.rs
+++ b/src/syntax/ast.rs
@@ -1430,29 +1430,26 @@ impl ForPattern {
}
node! {
- /// A module import: `import a, b, c from "utils.typ"`.
+ /// A module import: `import "utils.typ": a, b, c`.
ModuleImport
}
impl ModuleImport {
- /// The items to be imported.
- pub fn imports(&self) -> Imports {
- self.0
- .children()
- .find_map(|node| match node.kind() {
- SyntaxKind::Star => Some(Imports::Wildcard),
- SyntaxKind::ImportItems => {
- let items = node.children().filter_map(SyntaxNode::cast).collect();
- Some(Imports::Items(items))
- }
- _ => None,
- })
- .expect("module import is missing items")
+ /// The module or path from which the items should be imported.
+ pub fn source(&self) -> Expr {
+ self.0.cast_last_child().expect("module import is missing source")
}
- /// The path to the file that should be imported.
- pub fn path(&self) -> Expr {
- self.0.cast_last_child().expect("module import is missing path")
+ /// The items to be imported.
+ pub fn imports(&self) -> Option<Imports> {
+ self.0.children().find_map(|node| match node.kind() {
+ SyntaxKind::Star => Some(Imports::Wildcard),
+ SyntaxKind::ImportItems => {
+ let items = node.children().filter_map(SyntaxNode::cast).collect();
+ Some(Imports::Items(items))
+ }
+ _ => None,
+ })
}
}
@@ -1471,8 +1468,8 @@ node! {
}
impl ModuleInclude {
- /// The path to the file that should be included.
- pub fn path(&self) -> Expr {
+ /// The module or path from which the content should be included.
+ pub fn source(&self) -> Expr {
self.0.cast_last_child().expect("module include is missing path")
}
}
diff --git a/src/syntax/kind.rs b/src/syntax/kind.rs
index 27a8da0f..54d5c81d 100644
--- a/src/syntax/kind.rs
+++ b/src/syntax/kind.rs
@@ -125,8 +125,8 @@ pub enum SyntaxKind {
Import,
/// The `include` keyword.
Include,
- /// The `from` keyword.
- From,
+ /// The `as` keyword.
+ As,
/// Markup of which all lines must have a minimal indentation.
///
@@ -387,7 +387,7 @@ impl SyntaxKind {
Self::Return => "keyword `return`",
Self::Import => "keyword `import`",
Self::Include => "keyword `include`",
- Self::From => "keyword `from`",
+ Self::As => "keyword `as`",
Self::Markup { .. } => "markup",
Self::Text(_) => "text",
Self::Linebreak => "linebreak",
@@ -514,7 +514,7 @@ impl Hash for SyntaxKind {
Self::Return => {}
Self::Import => {}
Self::Include => {}
- Self::From => {}
+ Self::As => {}
Self::Markup { min_indent } => min_indent.hash(state),
Self::Text(s) => s.hash(state),
Self::Linebreak => {}
diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs
index a0b1e7d1..74be792f 100644
--- a/src/syntax/parser.rs
+++ b/src/syntax/parser.rs
@@ -237,7 +237,7 @@ impl<'s> Parser<'s> {
self.tokens.set_mode(match kind {
Group::Bracket | Group::Strong | Group::Emph => TokenMode::Markup,
Group::Math | Group::MathRow(_, _) => TokenMode::Math,
- Group::Brace | Group::Paren | Group::Expr | Group::Imports => TokenMode::Code,
+ Group::Brace | Group::Paren | Group::Expr => TokenMode::Code,
});
match kind {
@@ -249,7 +249,6 @@ impl<'s> Parser<'s> {
Group::Math => self.assert(SyntaxKind::Dollar),
Group::MathRow(l, _) => self.assert(SyntaxKind::Atom(l.into())),
Group::Expr => self.repeek(),
- Group::Imports => self.repeek(),
}
}
@@ -274,7 +273,6 @@ impl<'s> Parser<'s> {
Group::Math => Some((SyntaxKind::Dollar, true)),
Group::MathRow(_, r) => Some((SyntaxKind::Atom(r.into()), true)),
Group::Expr => Some((SyntaxKind::Semicolon, false)),
- Group::Imports => None,
} {
if self.current.as_ref() == Some(&end) {
// If another group closes after a group with the missing
@@ -346,7 +344,6 @@ impl<'s> Parser<'s> {
.next()
.map_or(false, |group| group.kind == Group::Math),
Some(SyntaxKind::Semicolon) => self.inside(Group::Expr),
- Some(SyntaxKind::From) => self.inside(Group::Imports),
Some(SyntaxKind::Atom(s)) => match s.as_str() {
")" => self.inside(Group::MathRow('(', ')')),
"}" => self.inside(Group::MathRow('{', '}')),
@@ -377,7 +374,6 @@ impl<'s> Parser<'s> {
match self.groups.last().map(|group| group.kind) {
Some(Group::Strong | Group::Emph) => n >= 2,
- Some(Group::Imports) => n >= 1,
Some(Group::Expr) if n >= 1 => {
// Allow else and method call to continue on next line.
self.groups.iter().nth_back(1).map(|group| group.kind)
@@ -541,8 +537,6 @@ pub enum Group {
MathRow(char, char),
/// A group ended by a semicolon or a line break: `;`, `\n`.
Expr,
- /// A group for import items, ended by a semicolon, line break or `from`.
- Imports,
}
impl Group {
diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs
index 900e0e67..7f557fac 100644
--- a/src/syntax/parsing.rs
+++ b/src/syntax/parsing.rs
@@ -1052,27 +1052,26 @@ fn for_pattern(p: &mut Parser) -> ParseResult {
fn module_import(p: &mut Parser) -> ParseResult {
p.perform(SyntaxKind::ModuleImport, |p| {
p.assert(SyntaxKind::Import);
+ expr(p)?;
- if !p.eat_if(SyntaxKind::Star) {
- // This is the list of identifiers scenario.
- p.perform(SyntaxKind::ImportItems, |p| {
- p.start_group(Group::Imports);
- let marker = p.marker();
- let items = collection(p, false).1;
- if items == 0 {
- p.expected("import items");
- }
- p.end_group();
+ if !p.eat_if(SyntaxKind::Colon) || p.eat_if(SyntaxKind::Star) {
+ return Ok(());
+ }
- marker.filter_children(p, |n| match n.kind() {
- SyntaxKind::Ident(_) | SyntaxKind::Comma => Ok(()),
- _ => Err("expected identifier"),
- });
+ // This is the list of identifiers scenario.
+ p.perform(SyntaxKind::ImportItems, |p| {
+ let marker = p.marker();
+ let items = collection(p, false).1;
+ if items == 0 {
+ p.expected("import items");
+ }
+ marker.filter_children(p, |n| match n.kind() {
+ SyntaxKind::Ident(_) | SyntaxKind::Comma => Ok(()),
+ _ => Err("expected identifier"),
});
- };
+ });
- p.expect(SyntaxKind::From)?;
- expr(p)
+ Ok(())
})
}
diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs
index 98b6d8a8..02bbd3a4 100644
--- a/src/syntax/tokens.rs
+++ b/src/syntax/tokens.rs
@@ -684,7 +684,7 @@ fn keyword(ident: &str) -> Option<SyntaxKind> {
"return" => SyntaxKind::Return,
"import" => SyntaxKind::Import,
"include" => SyntaxKind::Include,
- "from" => SyntaxKind::From,
+ "as" => SyntaxKind::As,
_ => return None,
})
}
diff --git a/tests/ref/compiler/import.png b/tests/ref/compiler/import.png
index dddbe408..89880086 100644
--- a/tests/ref/compiler/import.png
+++ b/tests/ref/compiler/import.png
Binary files differ
diff --git a/tests/src/benches.rs b/tests/src/benches.rs
index 2c693c7c..610b89a8 100644
--- a/tests/src/benches.rs
+++ b/tests/src/benches.rs
@@ -72,7 +72,8 @@ fn bench_typeset(iai: &mut Iai) {
let world = BenchWorld::new();
let route = typst::model::Route::default();
let module = typst::model::eval(world.track(), route.track(), &world.source).unwrap();
- iai.run(|| typst::model::typeset(world.track(), &module.content));
+ let content = module.content();
+ iai.run(|| typst::model::typeset(world.track(), &content));
}
fn bench_compile(iai: &mut Iai) {
diff --git a/tests/src/tests.rs b/tests/src/tests.rs
index 9dc7aa6d..16dea380 100644
--- a/tests/src/tests.rs
+++ b/tests/src/tests.rs
@@ -432,7 +432,7 @@ fn test_part(
let world = (world as &dyn World).track();
let route = typst::model::Route::default();
let module = typst::model::eval(world, route.track(), source).unwrap();
- println!("Model:\n{:#?}\n", module.content);
+ println!("Model:\n{:#?}\n", module.content());
}
let (mut frames, errors) = match typst::compile(world, source) {
diff --git a/tests/typ/compiler/block.typ b/tests/typ/compiler/block.typ
index 45f63f8e..7cf1f8be 100644
--- a/tests/typ/compiler/block.typ
+++ b/tests/typ/compiler/block.typ
@@ -79,7 +79,7 @@
---
// Double block creates a scope.
{{
- import b from "module.typ"
+ import "module.typ": b
test(b, 1)
}}
diff --git a/tests/typ/compiler/closure.typ b/tests/typ/compiler/closure.typ
index 2c6c1ea0..c7321204 100644
--- a/tests/typ/compiler/closure.typ
+++ b/tests/typ/compiler/closure.typ
@@ -62,7 +62,7 @@
{
let b = "module.typ"
let f() = {
- import b from b
+ import b: b
b
}
test(f(), 1)
diff --git a/tests/typ/compiler/field.typ b/tests/typ/compiler/field.typ
index 1235c84e..78439ae0 100644
--- a/tests/typ/compiler/field.typ
+++ b/tests/typ/compiler/field.typ
@@ -31,7 +31,7 @@
{false.ok}
---
-// Error: 29-32 unknown field "fun"
+// Error: 29-32 unknown field `fun`
#show heading: node => node.fun
= A
diff --git a/tests/typ/compiler/import.typ b/tests/typ/compiler/import.typ
index 0620403d..6f2ac459 100644
--- a/tests/typ/compiler/import.typ
+++ b/tests/typ/compiler/import.typ
@@ -1,116 +1,118 @@
// Test module imports.
+// Ref: false
---
-// Test importing semantics.
-
-// A named import.
-#import item from "module.typ"
-#test(item(1, 2), 3)
+// Test basic syntax and semantics.
+// Ref: true
// Test that this will be overwritten.
#let value = [foo]
// Import multiple things.
-#import fn, value from "module.typ"
+#import "module.typ": fn, value
#fn[Like and Subscribe!]
#value
+// Should output `bye`.
+// Stop at semicolon.
+#import "module.typ": a, c;bye
+
+---
+// An item import.
+#import "module.typ": item
+#test(item(1, 2), 3)
+
// Code mode
{
- import b from "module.typ"
+ import "module.typ": b
test(b, 1)
}
// A wildcard import.
-#import * from "module.typ"
+#import "module.typ": *
// It exists now!
-#d
+#test(d, 3)
-// Who needs whitespace anyways?
-#import*from"module.typ"
+---
+// A module import without items.
+#import "module.typ"
+#test(module.b, 1)
+#test((module.item)(1, 2), 3)
-// Should output `bye`.
-// Stop at semicolon.
-#import a, c from "module.typ";bye
+---
+// Who needs whitespace anyways?
+#import"module.typ":*
// Allow the trailing comma.
-#import a, c, from "module.typ"
+#import "module.typ": a, c,
---
-// Error: 19-21 failed to load file (is a directory)
-#import name from ""
+// Error: 9-11 failed to load file (is a directory)
+#import "": name
---
-// Error: 16-27 file not found (searched at typ/compiler/lib/0.2.1)
-#import * from "lib/0.2.1"
+// Error: 9-20 file not found (searched at typ/compiler/lib/0.2.1)
+#import "lib/0.2.1"
---
// Some non-text stuff.
-// Error: 16-37 file is not valid utf-8
-#import * from "../../res/rhino.png"
+// Error: 9-30 file is not valid utf-8
+#import "../../res/rhino.png"
---
// Unresolved import.
-// Error: 9-21 unresolved import
-#import non_existing from "module.typ"
+// Error: 23-35 unresolved import
+#import "module.typ": non_existing
---
// Cyclic import of this very file.
-// Error: 16-30 cyclic import
-#import * from "./import.typ"
+// Error: 9-23 cyclic import
+#import "./import.typ"
---
// Cyclic import in other file.
-#import * from "./modules/cycle1.typ"
+#import "./modules/cycle1.typ": *
This is never reached.
---
-// Error: 8 expected import items
-// Error: 8 expected keyword `from`
+// Error: 8 expected expression
#import
-// Error: 9-19 expected identifier, found string
-// Error: 19 expected keyword `from`
-#import "file.typ"
-
-// Error: 16-19 expected identifier, found string
-// Error: 22 expected keyword `from`
-#import afrom, "b", c
-
-// Error: 9 expected import items
-#import from "module.typ"
+---
+// Error: 26-29 expected identifier, found string
+#import "module.typ": a, "b", c
-// Error: 9-10 expected expression, found assignment operator
-// Error: 10 expected import items
-#import = from "module.typ"
+---
+// Error: 22 expected import items
+#import "module.typ":
-// Error: 15 expected expression
-#import * from
+---
+// Error: 23-24 expected expression, found assignment operator
+// Error: 24 expected import items
+#import "module.typ": =
+---
// An additional trailing comma.
-// Error: 17-18 expected expression, found comma
-#import a, b, c,, from "module.typ"
-
-// Error: 1-6 unexpected keyword `from`
-#from "module.typ"
+// Error: 31-32 expected expression, found comma
+#import "module.typ": a, b, c,,
+---
// Error: 2:2 expected semicolon or line break
-#import * from "module.typ
-"target
-
-// Error: 28 expected semicolon or line break
-#import * from "module.typ" ยง 0.2.1
+#import "module.typ
+"stuff
+---
// A star in the list.
-// Error: 12-13 expected expression, found star
-#import a, *, b from "module.typ"
+// Error: 26-27 expected expression, found star
+#import "module.typ": a, *, b
+---
// An item after a star.
-// Error: 10 expected keyword `from`
-#import *, a from "module.typ"
+// Error: 24 expected semicolon or line break
+#import "module.typ": *, a
---
-// Error: 9-13 expected identifier, found named pair
-#import a: 1 from ""
+// Error: 13-17 expected identifier, found named pair
+#import "": a: 1
diff --git a/tests/typ/compiler/modules/cycle1.typ b/tests/typ/compiler/modules/cycle1.typ
index a9c00f5e..02067b71 100644
--- a/tests/typ/compiler/modules/cycle1.typ
+++ b/tests/typ/compiler/modules/cycle1.typ
@@ -1,6 +1,6 @@
// Ref: false
-#import * from "cycle2.typ"
+#import "cycle2.typ": *
#let inaccessible = "wow"
This is the first element of an import cycle.
diff --git a/tests/typ/compiler/modules/cycle2.typ b/tests/typ/compiler/modules/cycle2.typ
index 204da519..191647db 100644
--- a/tests/typ/compiler/modules/cycle2.typ
+++ b/tests/typ/compiler/modules/cycle2.typ
@@ -1,6 +1,6 @@
// Ref: false
-#import * from "cycle1.typ"
+#import "cycle1.typ": *
#let val = "much cycle"
This is the second element of an import cycle.
diff --git a/tests/typ/compiler/show-node.typ b/tests/typ/compiler/show-node.typ
index 5dd7ad98..46663c68 100644
--- a/tests/typ/compiler/show-node.typ
+++ b/tests/typ/compiler/show-node.typ
@@ -78,7 +78,7 @@ Another text.
= Heading
---
-// Error: 25-29 unknown field "page"
+// Error: 25-29 unknown field `page`
#show heading: it => it.page
= Heading