use crate::ast::{ImportDecl, ImportKind};
use crate::diagnostic::ErrorClass;
use crate::lexer::token::TokenKind;
use crate::span::Spanned;
use smol_str::SmolStr;
use super::Parser;
pub(crate) fn parse_import(p: &mut Parser<'_>) -> Spanned<ImportDecl> {
let start = p.current_offset();
p.advance();
if p.at(&TokenKind::LBrace) {
parse_named_import(p, start)
} else {
parse_path_import(p, start)
}
}
fn parse_named_import(p: &mut Parser<'_>, start: usize) -> Spanned<ImportDecl> {
p.advance();
let mut names: Vec<Spanned<SmolStr>> = Vec::new();
while !p.at(&TokenKind::RBrace) && !p.at_eof() {
match p.peek_kind().clone() {
TokenKind::UpperIdent(s) => {
let tok = p.advance();
names.push(Spanned::new(s, tok.span));
}
_ => {
p.emit(
p.peek().span,
ErrorClass::UnexpectedToken,
"expected type name (UpperCase) in import list",
);
if !p.at(&TokenKind::RBrace) && !p.at_eof() {
p.advance();
}
continue;
}
}
if p.at(&TokenKind::Comma) {
p.advance();
}
}
if p.at(&TokenKind::RBrace) {
p.advance();
} else {
p.emit(
p.peek().span,
ErrorClass::UnexpectedToken,
"expected `}` to close import list",
);
}
if !p.at(&TokenKind::KwFrom) {
p.emit(
p.peek().span,
ErrorClass::UnexpectedToken,
"expected `from` after named import list",
);
let span = p.span_from(start);
return Spanned::new(
ImportDecl {
kind: ImportKind::Named { names },
path: Vec::new(),
version: None,
},
span,
);
}
p.advance();
let path = parse_namespace_path(p);
let version = parse_optional_version(p);
if p.at(&TokenKind::KwAs) {
let as_tok = p.advance();
p.emit(
as_tok.span,
ErrorClass::ImportNamedAliasedCombined,
"named imports cannot be combined with `as` alias",
);
if matches!(p.peek_kind(), TokenKind::UpperIdent(_)) {
p.advance();
}
}
let span = p.span_from(start);
Spanned::new(
ImportDecl {
kind: ImportKind::Named { names },
path,
version,
},
span,
)
}
fn parse_path_import(p: &mut Parser<'_>, start: usize) -> Spanned<ImportDecl> {
let path = parse_namespace_path(p);
let version = parse_optional_version(p);
let kind = if p.at(&TokenKind::KwAs) {
p.advance(); match p.peek_kind().clone() {
TokenKind::UpperIdent(s) => {
let tok = p.advance();
ImportKind::Aliased {
alias: Spanned::new(s, tok.span),
}
}
_ => {
p.emit(
p.peek().span,
ErrorClass::UnexpectedToken,
"expected alias name (UpperCase) after `as`",
);
ImportKind::Wildcard
}
}
} else {
ImportKind::Wildcard
};
let span = p.span_from(start);
Spanned::new(
ImportDecl {
kind,
path,
version,
},
span,
)
}
fn parse_namespace_path(p: &mut Parser<'_>) -> Vec<Spanned<SmolStr>> {
let mut path: Vec<Spanned<SmolStr>> = Vec::new();
match try_consume_lowercase_component(p) {
Some(spanned) => path.push(spanned),
None => {
p.emit(
p.peek().span,
ErrorClass::UnexpectedToken,
"expected namespace path",
);
return path;
}
}
while p.at(&TokenKind::Dot) {
p.advance(); match try_consume_lowercase_component(p) {
Some(spanned) => path.push(spanned),
None => {
p.emit(
p.peek().span,
ErrorClass::UnexpectedToken,
"expected identifier after `.` in import path",
);
break;
}
}
}
path
}
fn try_consume_lowercase_component(p: &mut Parser<'_>) -> Option<Spanned<SmolStr>> {
match p.peek_kind().clone() {
TokenKind::Ident(s) => {
let tok = p.advance();
Some(Spanned::new(s, tok.span))
}
ref kind if kind.is_keyword() => {
if let Some(name) = kind.as_field_name() {
let tok = p.advance();
Some(Spanned::new(name, tok.span))
} else {
None
}
}
_ => None,
}
}
fn parse_optional_version(p: &mut Parser<'_>) -> Option<Spanned<String>> {
if !p.at(&TokenKind::At) {
return None;
}
if !matches!(p.peek_nth(1).kind, TokenKind::Caret) {
return None;
}
let start = p.current_offset();
p.advance(); p.advance();
match p.peek_kind().clone() {
TokenKind::FloatLit(_) => {
let tok = p.advance(); let float_str = p.source_text(tok.span).to_string();
let (major_str, minor_str) = match float_str.split_once('.') {
Some((maj, min)) => (maj.to_string(), min.to_string()),
None => {
p.emit(
p.span_from(start),
ErrorClass::VersionInvalidSemver,
"invalid version format",
);
return None;
}
};
if !p.at(&TokenKind::Dot) {
let span = p.span_from(start);
p.emit(
span,
ErrorClass::VersionInvalidSemver,
format!(
"incomplete semver: ^{major_str}.{minor_str} — patch component required"
),
);
return None;
}
p.advance();
let patch = match p.peek_kind() {
TokenKind::DecInt(v) => {
let v = *v;
p.advance();
v
}
_ => {
p.emit(
p.peek().span,
ErrorClass::VersionInvalidSemver,
"expected patch version number",
);
return None;
}
};
let version_string = format!("^{major_str}.{minor_str}.{patch}");
let span = p.span_from(start);
Some(Spanned::new(version_string, span))
}
TokenKind::DecInt(major) => {
p.advance();
if !p.at(&TokenKind::Dot) {
p.emit(
p.peek().span,
ErrorClass::VersionInvalidSemver,
"expected `.` after major version",
);
return None;
}
p.advance();
let minor = match p.peek_kind() {
TokenKind::DecInt(v) => {
let v = *v;
p.advance();
v
}
_ => {
p.emit(
p.peek().span,
ErrorClass::VersionInvalidSemver,
"expected minor version number",
);
return None;
}
};
if !p.at(&TokenKind::Dot) {
let span = p.span_from(start);
p.emit(
span,
ErrorClass::VersionInvalidSemver,
format!("incomplete semver: ^{major}.{minor} — patch component required"),
);
return None;
}
p.advance();
let patch = match p.peek_kind() {
TokenKind::DecInt(v) => {
let v = *v;
p.advance();
v
}
_ => {
p.emit(
p.peek().span,
ErrorClass::VersionInvalidSemver,
"expected patch version number",
);
return None;
}
};
let version_string = format!("^{major}.{minor}.{patch}");
let span = p.span_from(start);
Some(Spanned::new(version_string, span))
}
_ => {
p.emit(
p.peek().span,
ErrorClass::VersionInvalidSemver,
"expected version number after `^`",
);
None
}
}
}