use kcl_error::KclErrorDetails;
use kcl_syntax::syntax_kind::SyntaxKind;
use super::Token;
use super::TokenStream;
use super::TokenType;
use crate::ModuleId;
use crate::SourceRange;
use crate::errors::KclError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum LexErrorKind {
UnknownToken,
UnterminatedString,
UnterminatedBlockComment,
}
#[derive(Debug, Clone)]
pub(crate) struct LexDiagnostic {
kind: LexErrorKind,
text: String,
range: SourceRange,
}
impl LexDiagnostic {
fn message(&self) -> String {
match self.kind {
LexErrorKind::UnknownToken => format!("found unknown token '{}'", self.text),
LexErrorKind::UnterminatedString => "unterminated string literal".to_owned(),
LexErrorKind::UnterminatedBlockComment => "unterminated block comment".to_owned(),
}
}
}
#[derive(Debug)]
pub(crate) struct LexResult {
pub(crate) tokens: TokenStream,
pub(crate) issues: Vec<LexDiagnostic>,
}
impl LexResult {
pub(crate) fn to_lexical_error(&self) -> Option<KclError> {
let issues = &self.issues;
let first = issues.first()?;
if issues.iter().all(|issue| issue.kind == LexErrorKind::UnknownToken) {
let ranges: Vec<SourceRange> = issues.iter().map(|issue| issue.range).collect();
let message = if issues.len() == 1 {
format!("found unknown token '{}'", first.text)
} else {
let list = issues
.iter()
.map(|issue| issue.text.as_str())
.collect::<Vec<_>>()
.join(", ");
format!("found unknown tokens [{list}]")
};
return Some(KclError::new_lexical(KclErrorDetails::new(message, ranges)));
}
Some(KclError::new_lexical(KclErrorDetails::new(
first.message(),
vec![first.range],
)))
}
}
pub(crate) fn lex_with_diagnostics(source: &str, module_id: ModuleId) -> LexResult {
let lexed = kcl_syntax::lexer::lex(source);
let mut tokens: Vec<Token> = Vec::with_capacity(lexed.len());
let mut issues: Vec<LexDiagnostic> = Vec::new();
for token in lexed.tokens() {
let kind = token.kind();
let range = token.range();
let text = token.text();
if let Some(error_kind) = recovery_kind(kind) {
issues.push(LexDiagnostic {
kind: error_kind,
text: text.to_owned(),
range: SourceRange::new(range.start, range.end, module_id),
});
}
tokens.push(Token::from_range(
range,
module_id,
syntax_kind_to_token_type(kind),
text.to_owned(),
));
}
legacy_import_paren_quirk(&mut tokens);
LexResult {
tokens: TokenStream::new(tokens),
issues,
}
}
fn recovery_kind(kind: SyntaxKind) -> Option<LexErrorKind> {
match kind {
SyntaxKind::Unknown => Some(LexErrorKind::UnknownToken),
SyntaxKind::UnterminatedString => Some(LexErrorKind::UnterminatedString),
SyntaxKind::UnterminatedBlockComment => Some(LexErrorKind::UnterminatedBlockComment),
_ => None,
}
}
fn legacy_import_paren_quirk(tokens: &mut [Token]) {
for i in 0..tokens.len() {
if tokens[i].token_type != TokenType::Keyword || tokens[i].value != "import" {
continue;
}
let import_end = tokens[i].end;
let followed_by_open_paren = tokens
.get(i + 1)
.is_some_and(|next| next.token_type == TokenType::Brace && next.value == "(" && next.start == import_end);
if followed_by_open_paren {
tokens[i].token_type = TokenType::Word;
}
}
}
pub(crate) fn syntax_kind_to_token_type(kind: SyntaxKind) -> TokenType {
match kind {
SyntaxKind::Number => TokenType::Number,
SyntaxKind::Word => TokenType::Word,
SyntaxKind::GtEq
| SyntaxKind::LtEq
| SyntaxKind::EqEq
| SyntaxKind::FatArrow
| SyntaxKind::BangEq
| SyntaxKind::PipeGt
| SyntaxKind::Star
| SyntaxKind::Plus
| SyntaxKind::Minus
| SyntaxKind::Slash
| SyntaxKind::Percent
| SyntaxKind::Eq
| SyntaxKind::Lt
| SyntaxKind::Gt
| SyntaxKind::Backslash
| SyntaxKind::Caret
| SyntaxKind::PipePipe
| SyntaxKind::AmpAmp
| SyntaxKind::Pipe
| SyntaxKind::Amp => TokenType::Operator,
SyntaxKind::String => TokenType::String,
SyntaxKind::IfKw
| SyntaxKind::ElseKw
| SyntaxKind::ForKw
| SyntaxKind::WhileKw
| SyntaxKind::ReturnKw
| SyntaxKind::BreakKw
| SyntaxKind::ContinueKw
| SyntaxKind::FnKw
| SyntaxKind::LetKw
| SyntaxKind::MutKw
| SyntaxKind::AsKw
| SyntaxKind::LoopKw
| SyntaxKind::TrueKw
| SyntaxKind::FalseKw
| SyntaxKind::NilKw
| SyntaxKind::AndKw
| SyntaxKind::OrKw
| SyntaxKind::NotKw
| SyntaxKind::VarKw
| SyntaxKind::ConstKw
| SyntaxKind::ImportKw
| SyntaxKind::ExportKw
| SyntaxKind::TypeKw
| SyntaxKind::InterfaceKw
| SyntaxKind::NewKw
| SyntaxKind::SelfKw
| SyntaxKind::RecordKw
| SyntaxKind::StructKw
| SyntaxKind::ObjectKw => TokenType::Keyword,
SyntaxKind::OpenParen
| SyntaxKind::CloseParen
| SyntaxKind::OpenBrace
| SyntaxKind::CloseBrace
| SyntaxKind::OpenBracket
| SyntaxKind::CloseBracket => TokenType::Brace,
SyntaxKind::Hash => TokenType::Hash,
SyntaxKind::Bang => TokenType::Bang,
SyntaxKind::Dollar => TokenType::Dollar,
SyntaxKind::Whitespace => TokenType::Whitespace,
SyntaxKind::Comma => TokenType::Comma,
SyntaxKind::Colon => TokenType::Colon,
SyntaxKind::DoubleColon => TokenType::DoubleColon,
SyntaxKind::Period => TokenType::Period,
SyntaxKind::DoublePeriod => TokenType::DoublePeriod,
SyntaxKind::DoublePeriodLessThan => TokenType::DoublePeriodLessThan,
SyntaxKind::LineComment => TokenType::LineComment,
SyntaxKind::BlockComment => TokenType::BlockComment,
SyntaxKind::UnterminatedString | SyntaxKind::UnterminatedBlockComment | SyntaxKind::Unknown => {
TokenType::Unknown
}
SyntaxKind::QuestionMark => TokenType::QuestionMark,
SyntaxKind::At => TokenType::At,
SyntaxKind::SemiColon => TokenType::SemiColon,
}
}