summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/eval/mod.rs18
-rw-r--r--src/parse/mod.rs76
-rw-r--r--src/parse/parser.rs6
-rw-r--r--src/parse/tokens.rs6
-rw-r--r--src/pretty.rs27
-rw-r--r--src/syntax/expr.rs35
-rw-r--r--src/syntax/token.rs12
-rw-r--r--src/syntax/visit.rs15
8 files changed, 173 insertions, 22 deletions
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index d841dbae..0af9dd6b 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 b4727fe9..9104b924 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<Node> {
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<Expr> {
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<Expr> {
// 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<CallArg>, 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<CallArg>) -> Vec<Ident> {
+/// Convert a collection into a list of identifiers, producing errors for
+/// anything other than identifiers.
+fn idents(p: &mut Parser, items: Vec<CallArg>) -> Vec<Ident> {
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<Expr> {
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> {
expr_let
}
+/// Parse an import expression.
+fn expr_import(p: &mut Parser) -> Option<Expr> {
+ 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<Expr> {
+ 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<Expr> {
let start = p.start();
diff --git a/src/parse/parser.rs b/src/parse/parser.rs
index 729d0a8d..6269ad73 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 a57db93b..62d2e68e 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<Token<'static>> {
"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 bf475bf6..397bbc38 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 97361fc3..fd106eb8 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<Box<Expr>>,
}
+/// 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<Expr>,
+}
+
+/// 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<Ident>),
+}
+
+/// 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<Expr>,
+}
+
/// 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 40e1d6d2..3484536d 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 9c1272ee..40d8e664 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);
+ }
}