diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 1d893ad9d..c3d532a74 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -728,33 +728,29 @@ fn import_expr(p: &mut Parser) -> Option { let start = p.next_start(); p.assert(Token::Import); + let imports = if p.eat_if(Token::Star) { + // This is the wildcard scenario. + Imports::Wildcard + } else { + // This is the list of identifiers scenario. + p.start_group(Group::Imports, TokenMode::Code); + let items = collection(p).0; + if items.is_empty() { + p.expected_at("import items", p.prev_end()); + } + p.end_group(); + Imports::Idents(idents(p, items)) + }; + let mut import_expr = None; - if let Some(path) = expr(p) { - let imports = if p.expect(Token::Using) { - if p.eat_if(Token::Star) { - // This is the wildcard scenario. - Imports::Wildcard - } else { - // This is the list of identifier scenario. - p.start_group(Group::Expr, TokenMode::Code); - let items = collection(p).0; - if items.is_empty() { - p.expected_at("import items", p.prev_end()); - } - - let idents = idents(p, items); - p.end_group(); - Imports::Idents(idents) - } - } else { - Imports::Idents(vec![]) - }; - - import_expr = Some(Expr::Import(ImportExpr { - span: p.span(start), - imports, - path: Box::new(path), - })); + if p.expect(Token::From) { + if let Some(path) = expr(p) { + import_expr = Some(Expr::Import(ImportExpr { + span: p.span(start), + imports, + path: Box::new(path), + })); + } } import_expr diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 8ea80d687..1e0f2f5d7 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -51,6 +51,8 @@ pub enum Group { Stmt, /// A group for a single expression, ended by a line break. Expr, + /// A group for import items, ended by a semicolon, line break or `from`. + Imports, } impl<'s> Parser<'s> { @@ -128,6 +130,7 @@ impl<'s> Parser<'s> { Group::Brace => self.assert(Token::LeftBrace), Group::Stmt => {} Group::Expr => {} + Group::Imports => {} } } @@ -149,6 +152,7 @@ impl<'s> Parser<'s> { Group::Brace => Some((Token::RightBrace, true)), Group::Stmt => Some((Token::Semicolon, false)), Group::Expr => None, + Group::Imports => None, } { if self.next == Some(end) { // Bump the delimeter and return. No need to rescan in this case. @@ -361,6 +365,7 @@ impl<'s> Parser<'s> { Token::RightBracket => self.inside(Group::Bracket), Token::RightBrace => self.inside(Group::Brace), Token::Semicolon => self.inside(Group::Stmt), + Token::From => self.inside(Group::Imports), Token::Space(n) => n >= 1 && self.stop_at_newline(), _ => false, } { @@ -371,7 +376,10 @@ impl<'s> Parser<'s> { /// Whether the active group ends at a newline. fn stop_at_newline(&self) -> bool { let active = self.groups.last().map(|group| group.kind); - matches!(active, Some(Group::Stmt) | Some(Group::Expr)) + matches!( + active, + Some(Group::Stmt) | Some(Group::Expr) | Some(Group::Imports) + ) } /// Whether we are inside the given group. diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index aebe7b70d..d979d0054 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -496,7 +496,7 @@ fn keyword(id: &str) -> Option> { "return" => Token::Return, "import" => Token::Import, "include" => Token::Include, - "using" => Token::Using, + "from" => Token::From, _ => return None, }) } diff --git a/src/pretty.rs b/src/pretty.rs index ff8841f3e..46b82eb7a 100644 --- a/src/pretty.rs +++ b/src/pretty.rs @@ -440,9 +440,9 @@ impl Pretty for ForPattern { impl Pretty for ImportExpr { fn pretty(&self, p: &mut Printer) { p.push_str("import "); - self.path.pretty(p); - p.push_str(" using "); self.imports.pretty(p); + p.push_str(" from "); + self.path.pretty(p); } } @@ -737,7 +737,7 @@ mod tests { roundtrip("#while x {y}"); roundtrip("#for x in y {z}"); roundtrip("#for k, x in y {z}"); - roundtrip("#import \"file.typ\" using *"); + roundtrip("#import * from \"file.typ\""); roundtrip("#include \"chapter1.typ\""); } diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index f5b79122d..c4462ff88 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -59,7 +59,7 @@ pub enum Expr { While(WhileExpr), /// A for loop expression: `for x in y { z }`. For(ForExpr), - /// An import expression: `import "utils.typ" using a, b, c`. + /// An import expression: `import a, b, c from "utils.typ"`. Import(ImportExpr), /// An include expression: `include "chapter1.typ"`. Include(IncludeExpr), @@ -466,7 +466,7 @@ pub struct LetExpr { pub init: Option>, } -/// An import expression: `import "utils.typ" using a, b, c`. +/// An import expression: `import a, b, c from "utils.typ"`. #[derive(Debug, Clone, PartialEq)] pub struct ImportExpr { /// The source code location. diff --git a/src/syntax/token.rs b/src/syntax/token.rs index 425dac108..c14cf9f72 100644 --- a/src/syntax/token.rs +++ b/src/syntax/token.rs @@ -101,8 +101,8 @@ pub enum Token<'s> { Import, /// The `include` keyword. Include, - /// The `using` keyword. - Using, + /// The `from` keyword. + From, /// One or more whitespace characters. /// /// The contained `usize` denotes the number of newlines that were contained @@ -254,7 +254,7 @@ impl<'s> Token<'s> { Self::Return => "keyword `return`", Self::Import => "keyword `import`", Self::Include => "keyword `include`", - Self::Using => "keyword `using`", + Self::From => "keyword `from`", Self::Space(_) => "space", Self::Text(_) => "text", Self::UnicodeEscape(_) => "unicode escape sequence", diff --git a/tests/ref/code/import.png b/tests/ref/code/import.png index 0a8114955..75d4adce4 100644 Binary files a/tests/ref/code/import.png and b/tests/ref/code/import.png differ diff --git a/tests/typ/code/block-scoping.typ b/tests/typ/code/block-scoping.typ index ed689f3d3..7970ee1bd 100644 --- a/tests/typ/code/block-scoping.typ +++ b/tests/typ/code/block-scoping.typ @@ -21,7 +21,7 @@ --- // Double block creates a scope. {{ - import "target.typ" using b + import b from "target.typ" test(b, 1) }} diff --git a/tests/typ/code/import.typ b/tests/typ/code/import.typ index 30dc556c7..18ebd499a 100644 --- a/tests/typ/code/import.typ +++ b/tests/typ/code/import.typ @@ -4,21 +4,21 @@ // Test importing semantics. // A named import. -#import "target.typ" using item +#import item from "target.typ" #test(item(1, 2), 3) // Test that this will be overwritten. #let value = [foo] // Import multiple things. -// Error: 28-29 expected expression, found comma -#import "target.typ" using ,fn, value +// Error: 9-10 expected expression, found comma +#import ,fn, value from "target.typ" #fn[Like and Subscribe!] #value // Code mode { - import "target.typ" using b + import b from "target.typ" test(b, 1) } @@ -29,91 +29,92 @@ #d // A wildcard import. -#import "target.typ" using * +#import * from "target.typ" // It exists now! #d +// Who needs whitespace anyways? +#import*from"target.typ" + +// Should output `Hi`. +// Stop at semicolon. +#import a, c from "target.typ";bye + +// Allow the trailing comma. +#import a, c, from "target.typ" + --- // Test bad imports. // Ref: false -// Error: 9-11 file not found -#import "" using name +// Error: 19-21 file not found +#import name from "" -// Error: 9-20 file not found -#import "lib/0.2.1" using * - -// Error: 9-20 file not found -#import "lib@0.2.1" using * +// Error: 16-27 file not found +#import * from "lib/0.2.1" // Some non-text stuff. -// Error: 9-30 file is not valid utf-8 -#import "../../res/rhino.png" using * +// Error: 16-37 file is not valid utf-8 +#import * from "../../res/rhino.png" // Unresolved import. -// Error: 28-40 unresolved import -#import "target.typ" using non_existing +// Error: 9-21 unresolved import +#import non_existing from "target.typ" // Cyclic import. -// Error: 9-34 cyclic import -#import "./importable/cycle1.typ" using * +// Error: 16-41 cyclic import +#import * from "./importable/cycle1.typ" --- -// Test syntax. +// Test bad syntax. -// Missing file. -// Error: 9-10 expected expression, found star -#import * +// Error: 2:8 expected import items +// Error: 1:8 expected keyword `from` +#import -// Should output `"target.typ"`. -// Error: 1-7 unexpected keyword `using` -#using "target.typ" +// Error: 2:9-2:19 expected identifier +// Error: 1:19 expected keyword `from` +#import "file.typ" -// Should output `target`. -// Error: 3:9-4:8 file not found -// Error: 3:8 expected semicolon or line break -// Error: 2:8 expected keyword `using` -#import "target.typ -using "target +// Error: 2:16-2:19 expected identifier +// Error: 1:22 expected keyword `from` +#import afrom, "b", c -// Should output `@ 0.2.1 using`. -// Error: 2:21 expected semicolon or line break -// Error: 1:21 expected keyword `using` -#import "target.typ" @ 0.2.1 using * +// Error: 8 expected import items +#import from "target.typ" -// Error: 3:21 expected keyword `using` -// Error: 2:21 expected semicolon or line break -// Error: 1:22-1:28 unexpected keyword `using` -#import "target.typ" #using * +// Error: 2:9-2:10 expected expression, found assignment operator +// Error: 1:10 expected import items +#import = from "target.typ" -// Error: 2:21 expected semicolon or line break -// Error: 1:21 expected keyword `using` -#import "target.typ" usinga,b,c - -// Error: 27 expected import items -#import "target.typ" using - -// Error: 2:28-2:29 expected expression, found assignment operator -// Error: 1:29 expected import items -#import "target.typ" using = - -// Allow the trailing comma. -#import "target.typ" using a, c, +// Error: 15 expected expression +#import * from // An additional trailing comma. -// Error: 36-37 expected expression, found comma -#import "target.typ" using a, b, c,, +// Error: 17-18 expected expression, found comma +#import a, b, c,, from "target.typ" -// Star in the list. -// Error: 2:31-2:32 expected expression, found star -// Error: 32-33 expected expression, found comma -#import "target.typ" using a, *, b +// Should output `"target.typ"`. +// Error: 1-6 unexpected keyword `from` +#from "target.typ" -// Stop at semicolon. -#import "target.typ" using a, c;Hi +// Should output `target`. +// Error: 2:16-3:2 file not found +// Error: 2:2 expected semicolon or line break +#import * from "target.typ +"target -// Who needs whitespace anyways? -#import "target.typ"using * -#import"target.typ"using* -#import "target.typ"using * +// Should output `@ 0.2.1`. +// Error: 28 expected semicolon or line break +#import * from "target.typ" @ 0.2.1 + +// A star in the list. +// Error: 2:12-2:13 expected expression, found star +// Error: 1:13-1:14 expected expression, found comma +#import a, *, b from "target.typ" + +// An item after a star. +// Should output `, a from "target.typ"`. +// Error: 10 expected keyword `from` +#import *, a from "target.typ" diff --git a/tests/typ/code/importable/cycle1.typ b/tests/typ/code/importable/cycle1.typ index e251686d8..ae755fa09 100644 --- a/tests/typ/code/importable/cycle1.typ +++ b/tests/typ/code/importable/cycle1.typ @@ -1,7 +1,7 @@ // Ref: false -// Error: 9-21 cyclic import -#import "cycle2.typ" using * +// Error: 16-28 cyclic import +#import * from "cycle2.typ" #let inaccessible = "wow" This is the first element of an import cycle. diff --git a/tests/typ/code/importable/cycle2.typ b/tests/typ/code/importable/cycle2.typ index 8071ec6b2..d4f945640 100644 --- a/tests/typ/code/importable/cycle2.typ +++ b/tests/typ/code/importable/cycle2.typ @@ -1,7 +1,7 @@ // Ref: false -// Error: 9-21 cyclic import -#import "cycle1.typ" using * +// Error: 16-28 cyclic import +#import * from "cycle1.typ" #let val = "much cycle" This is the second element of an import cycle. diff --git a/tools/support/typst.tmLanguage.json b/tools/support/typst.tmLanguage.json index 33e4c3db7..6c767b319 100644 --- a/tools/support/typst.tmLanguage.json +++ b/tools/support/typst.tmLanguage.json @@ -119,7 +119,7 @@ }, { "name": "keyword.other.typst", - "match": "(#)(as|in|with|from|using)\\b", + "match": "(#)(as|in|with|from)\\b", "captures": { "1": { "name": "punctuation.definition.keyword.typst" } } }, { @@ -231,7 +231,7 @@ }, { "name": "keyword.other.typst", - "match": "\\b(pub|let|as|in|with|import|include|from|using)\\b" + "match": "\\b(pub|let|as|in|with|import|include|from)\\b" }, { "include": "#constants" }, {