From 9f77f09aacd1fb0fd6138a6d16ed2755f6bfae3f Mon Sep 17 00:00:00 2001 From: Martin Haug Date: Sat, 29 May 2021 12:25:10 +0200 Subject: [PATCH] Parse import and include expressions Co-Authored-By: Laurenz --- src/eval/mod.rs | 18 +++++++ src/parse/mod.rs | 76 +++++++++++++++++++++++++---- src/parse/parser.rs | 6 --- src/parse/tokens.rs | 6 +-- src/pretty.rs | 27 ++++++++++ src/syntax/expr.rs | 35 +++++++++++++ src/syntax/token.rs | 12 +++-- src/syntax/visit.rs | 15 ++++++ tools/support/typst.tmLanguage.json | 13 ++++- 9 files changed, 184 insertions(+), 24 deletions(-) diff --git a/src/eval/mod.rs b/src/eval/mod.rs index d841dbae5..0af9dd6b1 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -146,6 +146,8 @@ impl Eval for Expr { Self::If(ref v) => v.eval(ctx), Self::While(ref v) => v.eval(ctx), Self::For(ref v) => v.eval(ctx), + Self::Import(ref v) => v.eval(ctx), + Self::Include(ref v) => v.eval(ctx), } } } @@ -565,3 +567,19 @@ impl Eval for ForExpr { } } } + +impl Eval for ImportExpr { + type Output = Value; + + fn eval(&self, _: &mut EvalContext) -> Self::Output { + todo!() + } +} + +impl Eval for IncludeExpr { + type Output = Value; + + fn eval(&self, _: &mut EvalContext) -> Self::Output { + todo!() + } +} diff --git a/src/parse/mod.rs b/src/parse/mod.rs index b4727fe96..9104b9247 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -74,9 +74,15 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option { Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)), // Hashtag + keyword / identifier. - Token::Ident(_) | Token::Let | Token::If | Token::While | Token::For => { + Token::Ident(_) + | Token::Let + | Token::If + | Token::While + | Token::For + | Token::Import + | Token::Include => { *at_start = false; - let stmt = token == Token::Let; + let stmt = token == Token::Let || token == Token::Import; let group = if stmt { Group::Stmt } else { Group::Expr }; p.start_group(group, TokenMode::Code); @@ -279,6 +285,8 @@ fn primary(p: &mut Parser, atomic: bool) -> Option { Some(Token::If) => expr_if(p), Some(Token::While) => expr_while(p), Some(Token::For) => expr_for(p), + Some(Token::Import) => expr_import(p), + Some(Token::Include) => expr_include(p), // Nothing. _ => { @@ -331,7 +339,7 @@ fn parenthesized(p: &mut Parser) -> Option { // Arrow means this is a closure's parameter list. if p.eat_if(Token::Arrow) { - let params = params(p, items); + let params = idents(p, items); let body = expr(p)?; return Some(Expr::Closure(ClosureExpr { span: span.join(body.span()), @@ -429,9 +437,9 @@ fn dict(p: &mut Parser, items: Vec, span: Span) -> Expr { Expr::Dict(DictExpr { span, items: items.collect() }) } -/// Convert a collection into a parameter list, producing errors for anything -/// other than identifiers. -fn params(p: &mut Parser, items: Vec) -> Vec { +/// Convert a collection into a list of identifiers, producing errors for +/// anything other than identifiers. +fn idents(p: &mut Parser, items: Vec) -> Vec { let items = items.into_iter().filter_map(|item| match item { CallArg::Pos(Expr::Ident(id)) => Some(id), _ => { @@ -511,24 +519,24 @@ fn expr_let(p: &mut Parser) -> Option { let mut expr_let = None; if let Some(binding) = ident(p) { // If a parenthesis follows, this is a function definition. - let mut parameters = None; + let mut params = None; if p.peek_direct() == Some(Token::LeftParen) { p.start_group(Group::Paren, TokenMode::Code); let items = collection(p).0; - parameters = Some(params(p, items)); + params = Some(idents(p, items)); p.end_group(); } let mut init = None; if p.eat_if(Token::Eq) { init = expr(p); - } else if parameters.is_some() { + } else if params.is_some() { // Function definitions must have a body. p.expected_at("body", p.end()); } // Rewrite into a closure expression if it's a function definition. - if let Some(params) = parameters { + if let Some(params) = params { let body = init?; init = Some(Expr::Closure(ClosureExpr { span: binding.span.join(body.span()), @@ -548,6 +556,54 @@ fn expr_let(p: &mut Parser) -> Option { expr_let } +/// Parse an import expression. +fn expr_import(p: &mut Parser) -> Option { + let start = p.start(); + p.assert(Token::Import); + + let mut expr_import = None; + if let Some(path) = expr(p) { + if p.expect(Token::Using) { + let imports = 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.end()); + } + + let idents = idents(p, items); + p.end_group(); + Imports::Idents(idents) + }; + + expr_import = Some(Expr::Import(ImportExpr { + span: p.span(start), + imports, + path: Box::new(path), + })); + } + } + + expr_import +} + +/// Parse an include expression. +fn expr_include(p: &mut Parser) -> Option { + let start = p.start(); + p.assert(Token::Include); + + expr(p).map(|path| { + Expr::Include(IncludeExpr { + span: p.span(start), + path: Box::new(path), + }) + }) +} + /// Parse an if expresion. fn expr_if(p: &mut Parser) -> Option { let start = p.start(); diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 729d0a8d4..6269ad73e 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -46,9 +46,6 @@ pub enum Group { Bracket, /// A curly-braced group: `{...}`. Brace, - /// A group ended by a chained subheader or a closing bracket: - /// `... >>`, `...]`. - Subheader, /// A group ended by a semicolon or a line break: `;`, `\n`. Stmt, /// A group for a single expression, ended by a line break. @@ -129,7 +126,6 @@ impl<'s> Parser<'s> { Group::Paren => self.assert(Token::LeftParen), Group::Bracket => self.assert(Token::LeftBracket), Group::Brace => self.assert(Token::LeftBrace), - Group::Subheader => {} Group::Stmt => {} Group::Expr => {} } @@ -152,7 +148,6 @@ impl<'s> Parser<'s> { Group::Paren => Some((Token::RightParen, true)), Group::Bracket => Some((Token::RightBracket, true)), Group::Brace => Some((Token::RightBrace, true)), - Group::Subheader => None, Group::Stmt => Some((Token::Semicolon, false)), Group::Expr => None, } { @@ -365,7 +360,6 @@ impl<'s> Parser<'s> { Token::RightBracket => self.inside(Group::Bracket), Token::RightBrace => self.inside(Group::Brace), Token::Semicolon => self.inside(Group::Stmt), - Token::Pipe => self.inside(Group::Subheader), Token::Space(n) => n >= 1 && self.stop_at_newline(), _ => false, } { diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index a57db93b1..62d2e68e4 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -124,7 +124,6 @@ impl<'s> Iterator for Tokens<'s> { ',' => Token::Comma, ';' => Token::Semicolon, ':' => Token::Colon, - '|' => Token::Pipe, '+' => Token::Plus, '-' => Token::Hyph, '*' => Token::Star, @@ -456,6 +455,9 @@ fn keyword(id: &str) -> Option> { "break" => Token::Break, "continue" => Token::Continue, "return" => Token::Return, + "import" => Token::Import, + "include" => Token::Include, + "using" => Token::Using, _ => return None, }) } @@ -617,7 +619,6 @@ mod tests { t!(Code: "," => Comma); t!(Code: ";" => Semicolon); t!(Code: ":" => Colon); - t!(Code: "|" => Pipe); t!(Code: "+" => Plus); t!(Code: "-" => Hyph); t!(Code[" a1"]: "*" => Star); @@ -637,7 +638,6 @@ mod tests { t!(Code: "=>" => Arrow); // Test combinations. - t!(Code: "|=>" => Pipe, Arrow); t!(Code: "<=>" => LtEq, Gt); t!(Code[" a/"]: "..." => Dots, Invalid(".")); diff --git a/src/pretty.rs b/src/pretty.rs index bf475bf62..397bbc38c 100644 --- a/src/pretty.rs +++ b/src/pretty.rs @@ -229,6 +229,8 @@ impl Pretty for Expr { Self::If(v) => v.pretty(p), Self::While(v) => v.pretty(p), Self::For(v) => v.pretty(p), + Self::Import(v) => v.pretty(p), + Self::Include(v) => v.pretty(p), } } } @@ -434,6 +436,31 @@ 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); + } +} + +impl Pretty for Imports { + fn pretty(&self, p: &mut Printer) { + match self { + Self::Wildcard => p.push('*'), + Self::Idents(idents) => p.join(idents, ", ", |item, p| item.pretty(p)), + } + } +} + +impl Pretty for IncludeExpr { + fn pretty(&self, p: &mut Printer) { + p.push_str("include "); + self.path.pretty(p); + } +} + impl Pretty for Ident { fn pretty(&self, p: &mut Printer) { p.push_str(self.as_str()); diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index 97361fc38..fd106eb81 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -56,6 +56,10 @@ 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`. + Import(ImportExpr), + /// An include expression: `include "chapter1.typ"`. + Include(IncludeExpr), } impl Expr { @@ -85,6 +89,8 @@ impl Expr { Self::If(ref v) => v.span, Self::While(ref v) => v.span, Self::For(ref v) => v.span, + Self::Import(ref v) => v.span, + Self::Include(ref v) => v.span, } } @@ -432,6 +438,35 @@ pub struct LetExpr { pub init: Option>, } +/// An import expression: `import "utils.typ" using a, b, c`. +#[derive(Debug, Clone, PartialEq)] +pub struct ImportExpr { + /// The source code location. + pub span: Span, + /// The items to be imported. + pub imports: Imports, + /// The location of the importable file. + pub path: Box, +} + +/// The items that ought to be imported from a file. +#[derive(Debug, Clone, PartialEq)] +pub enum Imports { + /// All items in the scope of the file should be imported. + Wildcard, + /// The specified identifiers from the file should be imported. + Idents(Vec), +} + +/// An include expression: `include "chapter1.typ"`. +#[derive(Debug, Clone, PartialEq)] +pub struct IncludeExpr { + /// The source code location. + pub span: Span, + /// The location of the file to be included. + pub path: Box, +} + /// An if-else expression: `if x { y } else { z }`. #[derive(Debug, Clone, PartialEq)] pub struct IfExpr { diff --git a/src/syntax/token.rs b/src/syntax/token.rs index 40e1d6d2b..3484536d6 100644 --- a/src/syntax/token.rs +++ b/src/syntax/token.rs @@ -32,8 +32,6 @@ pub enum Token<'s> { Semicolon, /// A colon: `:`. Colon, - /// A pipe: `|`. - Pipe, /// A plus: `+`. Plus, /// A hyphen: `-`. @@ -90,6 +88,12 @@ pub enum Token<'s> { Continue, /// The `return` keyword. Return, + /// The `import` keyword. + Import, + /// The `include` keyword. + Include, + /// The `using` keyword. + Using, /// One or more whitespace characters. /// /// The contained `usize` denotes the number of newlines that were contained @@ -201,7 +205,6 @@ impl<'s> Token<'s> { Self::Comma => "comma", Self::Semicolon => "semicolon", Self::Colon => "colon", - Self::Pipe => "pipe", Self::Plus => "plus", Self::Hyph => "minus", Self::Slash => "slash", @@ -231,6 +234,9 @@ impl<'s> Token<'s> { Self::Break => "keyword `break`", Self::Continue => "keyword `continue`", Self::Return => "keyword `return`", + Self::Import => "keyword `import`", + Self::Include => "keyword `include`", + Self::Using => "keyword `using`", Self::Space(_) => "space", Self::Text(_) => "text", Self::Raw(_) => "raw block", diff --git a/src/syntax/visit.rs b/src/syntax/visit.rs index 9c1272eeb..40d8e6644 100644 --- a/src/syntax/visit.rs +++ b/src/syntax/visit.rs @@ -87,6 +87,8 @@ visit! { Expr::If(e) => v.visit_if(e), Expr::While(e) => v.visit_while(e), Expr::For(e) => v.visit_for(e), + Expr::Import(e) => v.visit_import(e), + Expr::Include(e) => v.visit_include(e), } } @@ -189,4 +191,17 @@ visit! { v.visit_expr(&node.iter); v.visit_expr(&node.body); } + + fn visit_import(v, node: &ImportExpr) { + v.visit_expr(&node.path); + if let Imports::Idents(idents) = &node.imports { + for ident in idents { + v.visit_binding(ident); + } + } + } + + fn visit_include(v, node: &IncludeExpr) { + v.visit_expr(&node.path); + } } diff --git a/tools/support/typst.tmLanguage.json b/tools/support/typst.tmLanguage.json index 58c9f893f..3375a07df 100644 --- a/tools/support/typst.tmLanguage.json +++ b/tools/support/typst.tmLanguage.json @@ -119,7 +119,7 @@ }, { "name": "keyword.other.typst", - "match": "(#)(from|in)\\b", + "match": "(#)(in|using|as)\\b", "captures": { "1": { "name": "punctuation.definition.keyword.typst" } } }, { @@ -159,6 +159,15 @@ }, "patterns": [{ "include": "#code" }] }, + { + "begin": "(#)include\\b", + "end": "\n|(?=])", + "beginCaptures": { + "0": { "name": "keyword.control.include.typst" }, + "1": { "name": "punctuation.definition.keyword.typst" } + }, + "patterns": [{ "include": "#code" }] + }, { "comment": "Function name", "name": "entity.name.function.typst", @@ -216,7 +225,7 @@ }, { "name": "keyword.other.typst", - "match": "\\b(pub|let|import|from)\\b" + "match": "\\b(pub|let|import|include|using|as)\\b" }, { "include": "#constants" }, {