use chumsky::input::ValueInput;
use chumsky::prelude::*;
use crate::ast::{Ident, ParamConvention, PrimitiveType, TupleField, Type};
use crate::lexer::Token;
use super::ident_parser;
use super::span_from_simple;
fn primitive_from_name(name: &str) -> Option<PrimitiveType> {
match name {
"String" => Some(PrimitiveType::String),
"I32" => Some(PrimitiveType::I32),
"I64" => Some(PrimitiveType::I64),
"F32" => Some(PrimitiveType::F32),
"F64" => Some(PrimitiveType::F64),
"Boolean" => Some(PrimitiveType::Boolean),
"Path" => Some(PrimitiveType::Path),
"Regex" => Some(PrimitiveType::Regex),
"Never" => Some(PrimitiveType::Never),
_ => None,
}
}
#[expect(
clippy::too_many_lines,
reason = "parser combinator composition — local parsers are captured by closures and cannot be extracted without restructuring"
)]
pub(super) fn type_parser<'tokens, I>(
) -> impl Parser<'tokens, I, Type, extra::Err<Rich<'tokens, Token>>> + Clone
where
I: ValueInput<'tokens, Token = Token, Span = SimpleSpan>,
{
recursive(|type_ref| {
let ident_or_generic = ident_parser()
.separated_by(just(Token::DoubleColon))
.at_least(1)
.collect::<Vec<_>>()
.then(
type_ref
.clone()
.separated_by(just(Token::Comma))
.allow_trailing()
.at_least(1)
.collect::<Vec<_>>()
.delimited_by(just(Token::Lt), just(Token::Gt))
.or_not(),
)
.map_with(|(path, args), e| {
if args.is_none() && path.len() == 1 {
if let Some(first) = path.first() {
if let Some(prim) = primitive_from_name(first.name.as_str()) {
return Type::Primitive(prim);
}
}
}
let name_str = path
.iter()
.map(|id: &Ident| id.name.as_str())
.collect::<Vec<_>>()
.join("::");
let name = Ident::new(name_str, span_from_simple(e.span()));
if let Some(args) = args {
Type::Generic {
name,
args,
span: span_from_simple(e.span()),
}
} else {
Type::Ident(name)
}
});
let array_or_dict = type_ref
.clone()
.then(just(Token::Colon).ignore_then(type_ref.clone()).or_not())
.delimited_by(just(Token::LBracket), just(Token::RBracket))
.map(|(key_or_elem, value_opt)| {
if let Some(value) = value_opt {
Type::Dictionary {
key: Box::new(key_or_elem),
value: Box::new(value),
}
} else {
Type::Array(Box::new(key_or_elem))
}
});
let tuple_field = ident_parser()
.then_ignore(just(Token::Colon).labelled("':'"))
.then(type_ref.clone().labelled("type"))
.map_with(|(name, ty), e| TupleField {
name,
ty,
span: span_from_simple(e.span()),
});
let tuple = tuple_field
.separated_by(just(Token::Comma))
.at_least(1)
.allow_trailing()
.collect::<Vec<_>>()
.delimited_by(just(Token::LParen), just(Token::RParen))
.map(Type::Tuple);
let grouped_type = type_ref
.clone()
.delimited_by(just(Token::LParen), just(Token::RParen));
let base_type = choice((ident_or_generic, array_or_dict, tuple, grouped_type));
let optionable_type = base_type
.then(just(Token::Question).or_not())
.map(|(ty, opt)| {
if opt.is_some() {
Type::Optional(Box::new(ty))
} else {
ty
}
});
let closure_convention = choice((
just(Token::Mut).to(ParamConvention::Mut),
just(Token::Sink).to(ParamConvention::Sink),
))
.or_not()
.map(|c| c.unwrap_or(ParamConvention::Let));
let no_param_closure = just(Token::LParen)
.ignore_then(just(Token::RParen))
.ignore_then(just(Token::Arrow))
.ignore_then(type_ref.clone())
.map(|ret| Type::Closure {
params: vec![],
ret: Box::new(ret),
});
let param_closure = closure_convention
.clone()
.then(optionable_type.clone())
.separated_by(just(Token::Comma))
.at_least(1)
.collect::<Vec<_>>()
.then_ignore(just(Token::Arrow))
.then(type_ref)
.map(|(params, ret)| Type::Closure {
params,
ret: Box::new(ret),
});
choice((no_param_closure, param_closure, optionable_type)).labelled("type")
})
}