use super::{bail, Expr, ExprKind, ParserState, Result, Token};
pub fn parse_import_statement(state: &mut ParserState) -> Result<Expr> {
let start_span = crate::frontend::ast::Span { start: 0, end: 0 };
let module = parse_module_path(state)?;
let (final_module, items) = if matches!(state.tokens.peek(), Some((Token::As, _))) {
state.tokens.advance(); if let Some((Token::Identifier(alias), _)) = state.tokens.peek() {
let alias = alias.clone();
state.tokens.advance();
let parts: Vec<&str> = module.split('.').collect();
if parts.len() > 1 {
let parent_module = parts[..parts.len() - 1].join(".");
let item_name = parts[parts.len() - 1];
(
parent_module,
Some(vec![format!("{} as {}", item_name, alias)]),
)
} else {
(module.clone(), Some(vec![format!("self as {}", alias)]))
}
} else {
bail!("Expected identifier after 'as'");
}
} else {
(module, None)
};
Ok(Expr::new(
ExprKind::Import {
module: final_module,
items,
},
start_span,
))
}
pub fn parse_from_import_statement(state: &mut ParserState) -> Result<Expr> {
let start_span = crate::frontend::ast::Span { start: 0, end: 0 };
let module = parse_module_path(state)?;
state.tokens.expect(&Token::Import)?;
let items = if matches!(state.tokens.peek(), Some((Token::Star, _))) {
parse_wildcard_import_items(state)?
} else {
parse_named_import_items(state)?
};
Ok(Expr::new(ExprKind::Import { module, items }, start_span))
}
fn parse_wildcard_import_items(state: &mut ParserState) -> Result<Option<Vec<String>>> {
state.tokens.advance(); Ok(Some(vec![]))
}
fn parse_named_import_items(state: &mut ParserState) -> Result<Option<Vec<String>>> {
let mut import_items = Vec::new();
loop {
import_items.push(parse_import_item(state)?);
if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
state.tokens.advance();
} else {
break;
}
}
Ok(Some(import_items))
}
fn parse_import_item(state: &mut ParserState) -> Result<String> {
if let Some((Token::Identifier(name), _)) = state.tokens.peek() {
let mut item = name.clone();
state.tokens.advance();
if matches!(state.tokens.peek(), Some((Token::As, _))) {
state.tokens.advance();
if let Some((Token::Identifier(alias), _)) = state.tokens.peek() {
item = format!("{item} as {alias}");
state.tokens.advance();
} else {
bail!("Expected identifier after 'as'");
}
}
Ok(item)
} else {
bail!("Expected identifier in import list");
}
}
fn consume_import_comma(state: &mut ParserState) {
if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
state.tokens.advance();
}
}
fn parse_module_source(state: &mut ParserState) -> Result<String> {
if let Some((Token::String(path), _)) = state.tokens.peek() {
let path = path.clone();
state.tokens.advance();
Ok(path)
} else {
parse_module_path(state)
}
}
pub fn parse_js_style_import(state: &mut ParserState) -> Result<Expr> {
let start_span = crate::frontend::ast::Span { start: 0, end: 0 };
state.tokens.expect(&Token::LeftBrace)?;
let mut items = Vec::new();
while !matches!(state.tokens.peek(), Some((Token::RightBrace, _))) {
let item = parse_import_item(state)?;
items.push(item);
consume_import_comma(state);
}
state.tokens.expect(&Token::RightBrace)?;
state.tokens.expect(&Token::From)?;
let module = parse_module_source(state)?;
Ok(Expr::new(
ExprKind::Import {
module,
items: Some(items),
},
start_span,
))
}
fn parse_module_path(state: &mut ParserState) -> Result<String> {
if let Some((Token::String(path), _)) = state.tokens.peek() {
let path = path.clone();
state.tokens.advance();
return Ok(path);
}
let mut parts = Vec::new();
match state.tokens.peek() {
Some((Token::Identifier(name), _)) => {
parts.push(name.clone());
state.tokens.advance();
}
Some((token @ (Token::Self_ | Token::Super | Token::Crate), _)) => {
let name = match token {
Token::Self_ => "self",
Token::Super => "super",
Token::Crate => "crate",
_ => bail!("Unexpected token in module path"),
};
parts.push(name.to_string());
state.tokens.advance();
}
_ => bail!("Expected module path"),
}
while matches!(state.tokens.peek(), Some((Token::Dot, _)))
|| matches!(state.tokens.peek(), Some((Token::ColonColon, _)))
{
state.tokens.advance();
if let Some((Token::Identifier(name), _)) = state.tokens.peek() {
parts.push(name.clone());
state.tokens.advance();
} else {
bail!("Expected identifier after '.' or '::' in module path");
}
}
Ok(parts.join("::"))
}
#[cfg(test)]
mod tests {
use super::super::Parser;
#[test]
fn test_import_with_crate_keyword() {
let mut parser = Parser::new("import crate");
let result = parser.parse();
assert!(result.is_ok(), "Should parse 'import crate' statement");
}
#[test]
fn test_import_with_self_keyword() {
let mut parser = Parser::new("import self");
let result = parser.parse();
assert!(result.is_ok(), "Should parse 'import self' statement");
}
#[test]
fn test_import_with_super_keyword() {
let mut parser = Parser::new("import super");
let result = parser.parse();
assert!(result.is_ok(), "Should parse 'import super' statement");
}
#[test]
fn test_from_crate_import() {
let mut parser = Parser::new("from crate import utils");
let result = parser.parse();
assert!(result.is_ok(), "Should parse 'from crate import' statement");
}
#[test]
fn test_import_crate_with_path() {
let mut parser = Parser::new("import crate.utils");
let result = parser.parse();
assert!(
result.is_ok(),
"Should parse 'import crate.utils' statement"
);
}
#[test]
fn test_from_super_import() {
let mut parser = Parser::new("from super import foo");
let result = parser.parse();
assert!(result.is_ok(), "Should parse 'from super import' statement");
}
}
#[cfg(test)]
mod coverage_tests {
use super::super::Parser;
use crate::frontend::ast::ExprKind;
#[test]
fn test_import_simple() {
let mut parser = Parser::new("import std");
let result = parser.parse();
assert!(result.is_ok());
}
#[test]
fn test_import_with_dot_path() {
let mut parser = Parser::new("import std.collections");
let result = parser.parse();
assert!(result.is_ok());
}
#[test]
fn test_import_with_colons_path() {
let mut parser = Parser::new("import std::collections::HashMap");
let result = parser.parse();
assert!(result.is_ok());
}
#[test]
fn test_import_with_alias() {
let mut parser = Parser::new("import std.collections.HashMap as Map");
let result = parser.parse();
assert!(result.is_ok());
if let Ok(ast) = &result {
if let ExprKind::Block(exprs) = &ast.kind {
for expr in exprs {
if let ExprKind::Import { module, items } = &expr.kind {
assert_eq!(module, "std::collections");
assert!(items.is_some());
let items = items.as_ref().unwrap();
assert!(items[0].contains("as Map"));
}
}
}
}
}
#[test]
fn test_import_single_name_alias() {
let mut parser = Parser::new("import std as stdlib");
let result = parser.parse();
assert!(result.is_ok());
}
#[test]
fn test_from_import_single() {
let mut parser = Parser::new("from std import println");
let result = parser.parse();
assert!(result.is_ok());
}
#[test]
fn test_from_import_multiple() {
let mut parser = Parser::new("from std.collections import HashMap, HashSet");
let result = parser.parse();
assert!(result.is_ok());
if let Ok(ast) = &result {
if let ExprKind::Block(exprs) = &ast.kind {
for expr in exprs {
if let ExprKind::Import { items, .. } = &expr.kind {
assert!(items.is_some());
let items = items.as_ref().unwrap();
assert_eq!(items.len(), 2);
}
}
}
}
}
#[test]
fn test_from_import_wildcard() {
let mut parser = Parser::new("from std import *");
let result = parser.parse();
assert!(result.is_ok());
if let Ok(ast) = &result {
if let ExprKind::Block(exprs) = &ast.kind {
for expr in exprs {
if let ExprKind::Import { items, .. } = &expr.kind {
assert!(items.is_some());
let items = items.as_ref().unwrap();
assert!(items.is_empty());
}
}
}
}
}
#[test]
fn test_from_import_with_alias() {
let mut parser = Parser::new("from std import HashMap as Map");
let result = parser.parse();
assert!(result.is_ok());
}
#[test]
fn test_js_style_import() {
let mut parser = Parser::new("import { readFile, writeFile } from fs");
let result = parser.parse();
assert!(result.is_ok());
if let Ok(ast) = &result {
if let ExprKind::Block(exprs) = &ast.kind {
for expr in exprs {
if let ExprKind::Import { module, items } = &expr.kind {
assert_eq!(module, "fs");
assert!(items.is_some());
let items = items.as_ref().unwrap();
assert_eq!(items.len(), 2);
}
}
}
}
}
#[test]
fn test_js_style_import_with_alias() {
let mut parser = Parser::new("import { foo as bar } from baz");
let result = parser.parse();
assert!(result.is_ok());
}
#[test]
fn test_import_string_path() {
let mut parser = Parser::new(r#"import "path/to/module""#);
let result = parser.parse();
assert!(result.is_ok());
}
#[test]
fn test_from_import_string_path() {
let mut parser = Parser::new(r#"from "path/to/module" import foo"#);
let result = parser.parse();
assert!(result.is_ok());
}
#[test]
fn test_js_style_import_string_path() {
let mut parser = Parser::new(r#"import { foo } from "path/to/module""#);
let result = parser.parse();
assert!(result.is_ok());
}
#[test]
fn test_import_mixed_separators() {
let mut parser = Parser::new("import std::collections.vec");
let result = parser.parse();
assert!(result.is_ok());
}
}
#[cfg(test)]
mod mutation_tests {
use super::super::Parser;
#[test]
fn test_crate_keyword_deletion() {
let mut parser = Parser::new("import crate");
let result = parser.parse();
assert!(
result.is_ok(),
"Should parse 'import crate' using Token::Crate match arm"
);
}
}