From 29b31c4a5ac4cde311c4d38b3d70130e7d27ba76 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 3 Jan 2023 12:29:35 +0100 Subject: [PATCH] New import syntax --- library/src/compute/foundations.rs | 2 +- src/ide/highlight.rs | 13 ++- src/lib.rs | 2 +- src/model/eval.rs | 60 ++++++----- src/model/func.rs | 8 +- src/model/mod.rs | 2 + src/model/module.rs | 62 ++++++++++++ src/model/value.rs | 9 +- src/syntax/ast.rs | 35 +++---- src/syntax/kind.rs | 8 +- src/syntax/parser.rs | 8 +- src/syntax/parsing.rs | 33 +++--- src/syntax/tokens.rs | 2 +- tests/ref/compiler/import.png | Bin 4538 -> 4091 bytes tests/src/benches.rs | 3 +- tests/src/tests.rs | 2 +- tests/typ/compiler/block.typ | 2 +- tests/typ/compiler/closure.typ | 2 +- tests/typ/compiler/field.typ | 2 +- tests/typ/compiler/import.typ | 140 +++++++++++++------------- tests/typ/compiler/modules/cycle1.typ | 2 +- tests/typ/compiler/modules/cycle2.typ | 2 +- tests/typ/compiler/show-node.typ | 2 +- 23 files changed, 242 insertions(+), 159 deletions(-) create mode 100644 src/model/module.rs diff --git a/library/src/compute/foundations.rs b/library/src/compute/foundations.rs index 4e8b9d822..162ba8c49 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 { 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 1f52ae95a..321bf9a65 100644 --- a/src/ide/highlight.rs +++ b/src/ide/highlight.rs @@ -156,7 +156,7 @@ pub fn highlight(node: &LinkedNode) -> Option { 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 { | 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 4045c02de..e6410d84d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,7 +66,7 @@ pub fn compile(world: &(dyn World + 'static), source: &Source) -> SourceResult 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 { - let span = self.path().span(); - let path = self.path().eval(vm)?.cast::().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 { - let span = self.path().span(); - let path = self.path().eval(vm)?.cast::().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 { +fn import(vm: &Vm, source: Value, span: Span) -> SourceResult { + 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 878b717f4..98dc527c5 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 6ba8014c6..d84fe4642 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 000000000..c7cb7fafe --- /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); + +/// 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) -> 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, "", self.name()) + } +} diff --git a/src/model/value.rs b/src/model/value.rs index 0716985de..8103b2119 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 ccad77c24..4ef0b2a61 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 { + 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 27a8da0f5..54d5c81d9 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 a0b1e7d1e..74be792f0 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 900e0e678..7f557fac9 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 98b6d8a8e..02bbd3a49 100644 --- a/src/syntax/tokens.rs +++ b/src/syntax/tokens.rs @@ -684,7 +684,7 @@ fn keyword(ident: &str) -> Option { "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 dddbe408ebe1e5e7acfeb677b0950cfffc924253..898800862547954ae6385d637c230caf7d05d229 100644 GIT binary patch literal 4091 zcmb7HXIN8Pvkp=ePe6*4AVotHkQPv+XcQv72uLrH5<)LYkU$VrBvh#qRN4Uqkrp8$ z9S$H(5JU(?3DN}t=^et|=lgTN^4#Zp?*6g%+H0O!Yu285XWogqYpla|R^TiM1Y*LG65!+%@J3bsCv&h8AiaG)vEls}`etjbZii19BM zmA}|ykXbRE3wS1_Cq=gv9Pu35`KAab4Gk>utMvWK99(6kuEI$uGoyMie< z`C+z(VpMS6&E-KmL(^+t;$RukVetOd#ITZyu#&gPhs6{5BCpN>X8hZ;$FRUKsdx$o z0zp8Jz@S?!po@&4BnD6y2;>R^{g-8pF&J(l^z37Pw_7Ysr(hR3k{BL80-s@Wy>;;- z<0%FR$C<82w{8)q5VQeY<3QPt5jV2*;oxk6xon8+zGIKgK{sx}fmLh`?Vf)}p0i<95r7w;K3nmh)d=t72@Q?(HL z{mkcTcE`#pzy^cY5bN!c&5B$DN36kCvg5m7q;L!gf18Q2o)%T;E7(c$ zSXilckiL|csd^hY;VKrZ%=vj-@G~FZvVmA0^C4H%;QU9vR^sRx*M(;RJXA3|c@wX< zJtZEe$tz+w7TD&kMTA_)hmc70OVkb>+hg*}xiX?qy1_xVx=qOZ8Fs2}^YaVCCb5w#JwIR;?>mDg zLOs8C-l3Ak)*Wh$R_D^H1+F!wqs6rV{T#ozo^ZLE2I{-U`l1t4;A(0Wa;5%{eSeof z{a_%TG&O~?-n$^$w&QSY3G=>zyskDhFlH`ie>TuzQ6Rja*D>!IPN2X7b_MmEdAY$> z&42f}?UY}mXj@7LQuUKb%ez^=-|vr|k=ps#8(BBz`jy*9rNreXcEAQ)2`_7Gbm!bg3=gkt(PlorQWFAlKR{S9F#%W&q&jY|MPwXkh+GckB)x zS_~&KK#1a6hd&FP76k}A)xUI+&8S~>y2OI2&dxZy=&lWaL6pjx|4!YQr`w>6wYWWZ z0~jMNIevFvizE4qOq}eNnc8-3OXtE}oeLDbjESB&nGI)gFsI&}H@=WygN?5tTth&( za&~IT*lqcjV1t4=CUn>}>rlpnwUc(Ez^bLY21ax#}7?!x|kZyllde z_^v&R^mYjv7~=~7?W z|0XQ-xI-xzbDBNQw=Jc`)Ma(&xJzX~&iDAV-=MEpkBHe=g7%pKzu zj4SQO_0cv!67}ixjd8KAtcX4N0#nYc?7Caog_)7}Q{u2WFg?Z&_)Aysy5VX+ezsx& zoW??2Y2>YTxQyFKA~30*p(GvEeDMsyeMy@<_Xj@-$bZ5`8Lr|HCNI9pw{{n!8V5cT z!L9sOg84`1TW0Lrxf7WA}>*4sqX>>)k;e z9Sd|>KaDJ}!10}a{Va{yb7ESiRS9VdGVHr$Zr+DAx{FsX7!p(NT-uTD`&Hhs{OsO( zR@el&gOjGh*da2c&*Jks)tW$G;=(@Y*mQo8PAm4Tkz76wWp^@HfmwUHa_?&)6hg%soy-^5moXcNub#4WL8+ist=l*)RY zY86FROL44HQ5>k#?1Rn`ZoF*_~Z#MH8-|mE3Tf7Hy_c~p2Ke;(O<-C z@B9qu8-w)ApTWR}e_Sy3w**m0w+2nh>m=Oo`Ys_r!2|bw?|+ zS}h|V$I_MFzI87lz_O&=)t)mL@e#O06Gw-Mepgzi0t9U_J7NRWCjf<^=(jVkYW*K4 zAZ4LY6=dWy`d_cYd%kL`v0lxg3DncGG6!-KT5-j>qjodo^UbJzJBiwq89sLc>SnSK z|Ib!^exkU1Rx#aLdlr8#r^55X#-py(B&Lb_oIhLRm$K9(b9g=yW&x_hPQtJIJgQJkRTVOnK;B>3*i)XB|u8S8eD zJ8trNS;ahLk3z$BQRvNqq^Xl#JJd7uMkz+gUy^Q5?-;CY(-`0R4Ry*6i$=CNhC>N$ z-GUcHGlAUDYCWi}+>jwk|4qF9!Qry~W_l@uj#O)^4-MIQ4uu)i#@Q>8!zDXA-cCGd z!8~WE*V3|a{ZCr?PmEJn3?Q&;(Wm|kg%!R4sjI7dZ|h@aHFBTl_e5hxT3QumPJfnF zV;&}`hKr777gAHUjwCl7=;z*bb#sH7OzNfQzIpRAPQGc6ez=+}39w{*t80z_l%|i5 z4+@2{v&*1j@9+MmU1Q+^X;{MES5*ZQ2us@`RklV5mWbQS5fsYiYD8-{ z{f)Wo>}7_+JnB*M{Jcjw6xb#E%PPk_X;1Qu$wn@32gGcvD41 zMQLg2+qa9qs3k`e->dypu3i;_hmHSo=u8yx^Yr8jV_MJfYg$}bc%dHTYhgicdUyyp zAnx^(Wa&0QCo3}(3Weg-ggOHp9UUc2vS(!w+S(G5l6H+f?#cpS7W($#YO@Fz*YxCM zQgX5r?B(+Ea!P6{8QGlB@FZE1v8NI+fkvYpk+>y|@pwF-z0po2vXD^l{aNlQSwM89 z=bDPjX8YdWUQ!ZAZtLRU8>;8_dyl~+D+Oo{xISo=*U#I_OEfQECvj(M%f1#9Ia5>R zHFB0PQfecb+U+}DbE)BYZK`d$dSrN5UyO@hjbq>+V_cx1`?*Zb&AE}iy}jT+{@~sr z5{bOLyc^`DrKOmn`ufA>JImv>l;%sIcwLB`LmdJ^rBXvHD3sLbzD(7@!9nb=At~s$ z=5Tnk*TL3e#KU*P!%Q;`Fa&_wA) z1QU?nr9^rsk=}Fnx$Ca`-n!?lclX+B_MSfTm+${(qII>^m>8}wfIuK7^@j*O5QvHf z_;08C14!kHxeh=e)(~}slD^N+jT~POcAL6LV##!U%1&}0n$I6UfZ}y1q93>3OM}KL zU4YQUS@0^dhbhuNOdSTJ`S|Y9t%?b_UZ%F36-)8Y>AJ@HyCe)6dqb9AjYX}8imz{f zr})uhkK`uB#gdAkUSA5{aCg@*$8W9p2qQm*09NoHPs{6INU-^ZL@E#-1VV#Aa1aOr z3I>A|X+gZyp#M_b(q!Yak?l(Njh7nE3ycvG45p(&EAsME|3L-0K!*=jRHUW(0}j59 zp$Of**J?wUM&oMG&XxX&b8n}tgL*PGMRu8Wc%16ByV_^d74Rm3yw#h?&9=|PfF`^g z-Zj8r?De6}?PGThmseet3PmpMoO^c299r+%c_*A9*&H#QLVSgtwmL?WN&S5kjyNYY)wzDLf6Qw{2siZmf+5 z^K9p3eHKrS`tTxdK4yDbg0;UvN-Naf{cBA5c}E)fPDfT7>V&9l8Zpu()JhMtf@Dj} z<_8oNTN!>muqk-nXd?J*vFV@F-C|9gDN0hh473&6ARu;}MZ=9u(Lf)T>4k%0^aWb< z0X^Dh*KYIUhq2wfd9Yv`b_&A~OVpbEA`yThuaDF;Y~@D}&uH!!jmi5BW`14qd^~O= zz{p9HUVX*58OFN#(o$#k4bdp!E~Qjl+FcbRA}K=Ad`?s?p=3!`$TZkJez+hTJ%LLd zOPne8Oag0)p4}vL7#kh(tW;%LW%qJS`R4|h!m&5K4ny>zkgQ&gv}VgrC`(r5vrpvD z-6~TRlLJ{%KTuTXI9gA+UHdmHUW+ny!?>hhSfd9G!%z0o_k?<#qWuA-@0zp!2yy_A zhEI@DJ-~jL@)nQ;U2l>7%%hCSC2HHd?UHX`%l@hDr0ZHpZj6HHa_x%_K;*n_@4Cu= z?bCz(NvmIyEw>3LthB9TJF+2~4k4xS&B=z3t7USV))BEYsT$&zpOlsok-KO)h zLJl04MEF7>n1;5yOy<*FcIeO9BU)!W*vIg+hf4((NVe!qdoKR)__#8Li_wsz4A(vV zPJRACA000E5C6e$8qPLXYte<#5vy~aALfBf2? zBR9a8oz>uTd^k8XtzhMmrQ*k)epKBm334J4TU9h5YScw+`7o*?E~15l3_H&OD#4vG ziv}eUIP#{hd@oPEWBXp0wpdTUMl7RVr%d|I!u-B_N~>ZL1GwN0^B0pHPfE)7 z7r*0&{KAW~dyt#^#-bbw)iC(|m-m8>b+R{w4PtzJ&3>kT)YnwKlH&{c2AAY-^X$y( zkM(-JfFlLkkiiDqz1SF2`dg(#3aa0mHyXASiy!DW4So~OsR0!4pI{ruFVrw2i*(O7?4Ik|Wj}L+E%v->o?T|_2i&#pa{WW?*=CanPfAXA`8PTDTq90t zU08?wO!u%kMLl6sDNf~!65-eT^wP85xNmySNg2NO#Kwh^KK91gbhmc%!6VurB8RGD zK_sH*(hnBDz{ujQoNs`2%C!x#z^F&LqC2@#!6tSl3skOkN}c)p2K2{1z*0pv9^N#) zmL%5i=8F9^U|4_qMUP{gC9<+2hJuHGl(R}IuuEF#P;k%`Nh;(Smg01HjozP*>*1*N zxyc7$P)m8^sMTq4Kspe4zli)AQW=HZD^YH3KkQ3 z(Zmto^4c|A{D(2*GD+l(3p+D3yN7)*jc*PTg#G;*(UXw*YnY_8DnB(42Hb}IixCo4;vHU>!j$Fl#EX(3AX~|+^wJ+5QfF`75vZazc&uY9f>mN8?7k9*>5vEz86!v*$IRX zw&|6n6Up4=CWzQ({!{ot+yGtml05A9`p7-)xMWUcvqLdWpJZPuBiN;SpPQkNU|;uj zx2*6P^k$RydqcZg@_b=&LIQ$rnCwo6Mw3~|0dd}^-+QmQmc?{>&Iy-E<>2!uBbTx5 z=kbbw4qnArSh}nsKL0cJyQ*-1jGBq00lxh7DW;NcGdyh1Bs&lbb*KoB>QkLkz->z; zlGUMYSeYkX-dSM6Q9lazys^QgDc;!ityO*WKhDsUy@9P@B^E?uHN3-hNMyc)G5UVRGlW2gZTu8Idmz zOB#3Tb&8+6MANP}BHQ3S;dDmrfP}uQ4f9FZ@N+zqw#(yt>IbMWl(7{A^K~#% z;Y;^DZ~uVKCr!X!>bF>6%05TrPb+~7&KaFX@%@LJPyK)ZBFJ_pN9}NY)N54ZML>AiSppUt0BiYCgB%Ij(BRuU0U~ z?7|l#1OjHJ!F3v9K(YliMZRn6m=L(o_7Mn!fiibL_bHRGY)$ipF9|^SG0xveJJkIU z6qmpu4%XaMN*E5(bJS|zm@kikzzuLeBAv2C+d zEV`?hxb`@x=0v`V0wDUcIOM<}IO1Q$bloc$f?frhWhz47d ziBJ!Wiy*3XU2bJ=*-8##`nz5nSVvydTr2NA2jGk638n2{JVC1aU;8GlWs zzrJYq}ktOgS#D1PBgt;R8kcq+-&O$ChQwg{g?~!1w6<$>lw0Bz*L0 zPo?-*6h)m`VdN95gF+`;pqKLH-J&hSQ@MA<#|#uS_Vl|o3%u4|z(_Q`VapN||HL$? z(=9cyUYVs55Bgv9@^8#D@gWTw^vjmvztGu(cD&247AhW|JnHkzVEuxkB4Ggm>+wn( z{KbRtuDF(&^Z7Jtz_Sp6mVVwvth}y2Ixj9Hf1K3ky ztE1TER@mpl_D)3AnHIV6&!>o>&VE;?)ts7wJmb7e(>Odr6r#^n2s((+Wq<# zP2kM`!|h#;KxlyV!vWe4;r;u-e>)Sj^d%M4*ZWh>PI-b(e#di3C9+u*!96@Z!^6VJ zOsl&`9UZFZb7MK+^DNw{1oYY2nXLEbeUIfnoN4_7Wn~oRg@c2#Rq#uK%gR8Z>s&{U z{L3t&+t$1~{}m~xt8CO_!IDsRh7>~BH;{k&40)yvp^rjE1DVvBqusTx7g&e2RCPZHpHfs*NhNAMi)s_Y|{5dh_j#Y2An{Ap!v-EJk`Re2@;LrqOheLWY7YEL$}>E-d>FxD^${5KK*?KJi; zB?Arr4^{oU^}!*bufKoh*DoLqkpQF@a%>Z^x99c#e@vyo9Fo8-SLe3Ssh)CucxdR2 zoIhs5f371wD=Q0!gJBIlJw1miDYse=fBO@;L?oKO>z`j*TAH7~k=B`! zkrB%&%3o&ju=4^Zr}xa)b7!-AWHKESQ=|VrZrpLY##NxJvPe5;*8DGHz}T#>a?gm646;QThz2pFzMk_oC$$cio;t*@^OzRi*K-ZIV&*izTg>CMgMHZ&Z~ykomHk)4$lg node.fun = A diff --git a/tests/typ/compiler/import.typ b/tests/typ/compiler/import.typ index 0620403dd..6f2ac459c 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) +--- +// A module import without items. +#import "module.typ" +#test(module.b, 1) +#test((module.item)(1, 2), 3) + +--- // Who needs whitespace anyways? -#import*from"module.typ" - -// Should output `bye`. -// Stop at semicolon. -#import a, c from "module.typ";bye +#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: 9-10 expected expression, found assignment operator -// Error: 10 expected import items -#import = from "module.typ" - -// Error: 15 expected expression -#import * from - -// 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: 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 - -// A star in the list. -// Error: 12-13 expected expression, found star -#import a, *, b from "module.typ" - -// An item after a star. -// Error: 10 expected keyword `from` -#import *, a from "module.typ" +--- +// Error: 26-29 expected identifier, found string +#import "module.typ": a, "b", c --- -// Error: 9-13 expected identifier, found named pair -#import a: 1 from "" +// Error: 22 expected import items +#import "module.typ": + +--- +// Error: 23-24 expected expression, found assignment operator +// Error: 24 expected import items +#import "module.typ": = + +--- +// An additional trailing comma. +// Error: 31-32 expected expression, found comma +#import "module.typ": a, b, c,, + +--- +// Error: 2:2 expected semicolon or line break +#import "module.typ +"stuff + +--- +// A star in the list. +// Error: 26-27 expected expression, found star +#import "module.typ": a, *, b + +--- +// An item after a star. +// Error: 24 expected semicolon or line break +#import "module.typ": *, a + +--- +// 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 a9c00f5e7..02067b715 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 204da5198..191647db2 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 5dd7ad984..46663c683 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