use crate::ParseError;
use crate::operand::ungrouped;
use crate::operand::unit_tail;
use crate::parser::Parser;
use crate::token::is_word;
use crate::token::token_text;
use crate::where_clause::where_locals;
use voltaic_ast::Clause;
use voltaic_ast::ClauseBody;
use voltaic_ast::Constraint;
use voltaic_ast::Ident;
use voltaic_ast::Param;
use voltaic_ast::Pattern;
use voltaic_ast::Signature;
use voltaic_ast::TypeApp;
use voltaic_ast::TypeExpr;
use voltaic_ast::primitive_type;
use voltaic_diag::Code;
use voltaic_diag::Span;
use voltaic_lexer::Keyword;
use voltaic_lexer::TokenKind;
pub(crate) fn can_end_a_type(kind: &TokenKind) -> bool {
match kind {
TokenKind::Ident(_)
| TokenKind::Gt
| TokenKind::Shr
| TokenKind::Compose
| TokenKind::RParen
| TokenKind::RBracket => true,
TokenKind::Kw(Keyword::SelfType) => true,
_ => false,
}
}
pub(crate) fn capture_type(
p: &mut Parser,
message: &str,
stop: TypeStop,
) -> Result<TypeExpr, ParseError> {
let start = p.peek_span().0;
let mut depth: i32 = 0;
let mut text = String::new();
let mut group_followed_a_name = false;
let mut prev_was_a_name = false;
let mut closed_a_named_group = false;
let mut prev_kind = TokenKind::Eof;
loop {
let kind = &p.peek().kind;
if depth == 0
&& stop == TypeStop::Atom
&& !text.is_empty()
&& p.peek().ws_before
&& can_end_a_type(&prev_kind)
{
break;
}
let at_eof = match kind {
TokenKind::Eof => true,
_ => false,
};
if at_eof {
if depth > 0 {
return Err(ParseError::with_help(
Code::ExpectedType,
"this type's brackets are not closed",
"a `<`, `(`, `[` or `{` in a type is closed on the same line — a type runs to the end of its line and no further",
(start, p.peek_span().1),
));
}
break;
}
let crosses_a_line = match kind {
TokenKind::Newline | TokenKind::Indent | TokenKind::Dedent => depth > 0,
_ => false,
};
if crosses_a_line {
return Err(ParseError::with_help(
Code::ExpectedType,
"this type's brackets are not closed on this line",
"a `<`, `(`, `[` or `{` in a type is closed on the same line — a type runs to the end of its line and no further",
(start, p.peek_span().0),
));
}
if depth == 0 {
let ends = match kind {
TokenKind::Comma | TokenKind::RParen | TokenKind::Eq => true,
TokenKind::RBrace | TokenKind::Or | TokenKind::Pipe => true,
TokenKind::Str { .. } | TokenKind::RawStr { .. } => true,
TokenKind::Kw(Keyword::As) => true,
TokenKind::Kw(Keyword::Where) => true,
TokenKind::Arrow => !closed_a_named_group,
TokenKind::Newline | TokenKind::Indent | TokenKind::Dedent | TokenKind::Eof => true,
TokenKind::Gt | TokenKind::Shr | TokenKind::Compose | TokenKind::RBracket => true,
_ => false,
};
if ends {
break;
}
}
match kind {
TokenKind::LParen => {
if depth == 0 {
group_followed_a_name = prev_was_a_name;
}
depth += 1;
}
TokenKind::Lt | TokenKind::LBracket | TokenKind::LBrace => depth += 1,
TokenKind::Gt | TokenKind::RParen | TokenKind::RBracket | TokenKind::RBrace => depth -= 1,
TokenKind::Shr => depth -= 2,
TokenKind::Compose => depth -= 3,
_ => (),
};
let is_rparen = match kind {
TokenKind::RParen => true,
_ => false,
};
closed_a_named_group = is_rparen && group_followed_a_name;
prev_was_a_name = match kind {
TokenKind::Ident(_) | TokenKind::Kw(Keyword::Fn) => true,
_ => false,
};
if !text.is_empty() && p.peek().ws_before {
text.push(' ');
}
text.push_str(&token_text(kind));
prev_kind = kind.clone();
p.advance();
}
if text.is_empty() {
return Err(ParseError::r#with(
Code::ExpectedType,
message,
p.peek_span(),
));
}
Ok(TypeExpr {
text: ungrouped(&text),
span: (start, p.prev_span().1),
})
}
pub(super) fn clause(
row: Vec<Pattern>,
name: &Ident,
returns_unit: bool,
p: &mut Parser,
) -> Result<Clause, ParseError> {
let start = row.first().map_or(p.peek_span().0, |first| first.span().0);
if p.opens_a_guard_list() {
p.advance();
p.advance();
let rungs = p.guards()?;
p.expect(&TokenKind::Dedent, "expected the guard list to end")?;
let wheres = where_locals(&row, p)?;
return Ok(Clause {
patterns: row,
body: ClauseBody::Guarded(rungs),
wheres,
span: (start, p.prev_span().1),
trailing: None,
});
}
p.expect(
&TokenKind::Eq,
"expected `=` after the equation's parameters, or an indented `|` guard list",
)?;
if !p.check(&TokenKind::Newline) {
let body = p.inline_body()?;
let trailing = p.newline_with_trailing("expected a line break after the body")?;
let wheres = where_locals(&row, p)?;
return Ok(Clause {
patterns: row,
body: ClauseBody::Plain(body),
wheres,
span: (start, p.prev_span().1),
trailing,
});
}
let trailing = p.newline_with_trailing("expected a line break after `=`")?;
p.expect_with(
Code::EmptyBody,
&TokenKind::Indent,
"expected an indented body",
)?;
let reported = p.r#errors.len();
let block = p.statements()?;
if returns_unit && p.r#errors.len() == reported {
unit_tail(name, &block)?;
}
let wheres = where_locals(&row, p)?;
Ok(Clause {
patterns: row,
body: ClauseBody::Plain(block.clone()),
wheres,
span: (start, p.prev_span().1),
trailing,
})
}
pub(crate) fn rust_generic(name: &str) -> String {
if name.starts_with('\'') {
return name.to_string();
}
name.to_uppercase()
}
pub(crate) fn signature_from(
spine: &[TypeExpr],
bound: &[Pattern],
span: Span,
) -> Result<Signature, ParseError> {
let split = spine
.split_last()
.expect("parse_spine returns at least one type");
let ret = split.0;
let arrow_types = split.1;
let arrows = arrow_types.len();
let names = bound.len();
if arrows != names {
let detail = if names < arrows {
format!(
"the signature takes {}, but this equation binds {}",
arrows, names
)
} else {
format!(
"this equation binds {}, but the signature takes {}",
names, arrows
)
};
let message = format!(
"the signature and the equation disagree about arity: {}",
detail
);
return Err(ParseError::r#with(Code::ExpectedName, message, span));
}
let mut params = Vec::new();
for entry in bound.iter().zip(arrow_types).enumerate() {
let i = entry.0;
let row = entry.1;
let binder = row.0;
let ty = row.1;
let lends_its_name = match binder {
Pattern::Binding(ident) => !ident.text.chars().next().is_some_and(char::is_uppercase),
_ => false,
};
let name = if lends_its_name {
match binder {
Pattern::Binding(ident) => ident.clone(),
_ => Ident {
text: format!("__vlt_a{}", i),
span: binder.span(),
},
}
} else {
Ident {
text: format!("__vlt_a{}", i),
span: binder.span(),
}
};
params.push(Param {
name,
ty: Some(ty.clone()),
is_mut: false,
span: binder.span(),
});
}
Ok(Signature {
params,
ret: Some(ret.clone()),
generics: Vec::new(),
is_const: false,
is_async: false,
is_unsafe: false,
bounds: Vec::new(),
r#given: None,
predicates: None,
span,
})
}
pub(crate) fn signature_unsigned(bound: &[Pattern], span: Span) -> Signature {
let mut params = Vec::new();
for entry in bound.iter().enumerate() {
let i = entry.0;
let binder = entry.1;
let lends_its_name = match binder {
Pattern::Binding(ident) => !ident.text.chars().next().is_some_and(char::is_uppercase),
_ => false,
};
let name = if lends_its_name {
match binder {
Pattern::Binding(ident) => ident.clone(),
_ => Ident {
text: format!("__vlt_a{}", i),
span: binder.span(),
},
}
} else {
Ident {
text: format!("__vlt_a{}", i),
span: binder.span(),
}
};
params.push(Param {
name,
ty: None,
is_mut: false,
span: binder.span(),
});
}
Signature {
params,
ret: None,
generics: Vec::new(),
is_const: false,
is_async: false,
is_unsafe: false,
bounds: Vec::new(),
r#given: None,
predicates: None,
span,
}
}
#[cfg(test)]
fn ty(text: &str) -> TypeExpr {
TypeExpr {
text: text.to_string(),
span: (0, 0),
}
}
#[cfg(test)]
fn id(text: &str) -> Pattern {
Pattern::Binding(Ident {
text: text.to_string(),
span: (0, 0),
})
}
#[derive(Debug, Clone)]
pub(crate) struct SignatureHead {
pub types: Vec<TypeExpr>,
pub bounds: Vec<Constraint>,
pub r#given: Option<String>,
pub predicates: Option<String>,
pub is_const: bool,
pub is_async: bool,
pub is_unsafe: bool,
}
pub(crate) fn signature_only(spine: &[TypeExpr], span: Span) -> Signature {
let split = spine
.split_last()
.expect("parse_spine returns at least one type");
let params: Vec<Param> = split
.1
.iter()
.enumerate()
.map(|pair| {
let i = pair.0;
let ty = pair.1;
Param {
name: Ident {
text: format!("__vlt_a{}", i),
span: ty.span,
},
ty: Some(ty.clone()),
is_mut: false,
span: ty.span,
}
})
.collect();
Signature {
params,
ret: Some(split.0.clone()),
generics: Vec::new(),
is_const: false,
is_async: false,
is_unsafe: false,
bounds: Vec::new(),
r#given: None,
predicates: None,
span,
}
}
pub(crate) fn starts_a_field_type(kind: &TokenKind) -> bool {
match kind {
TokenKind::Ident(_)
| TokenKind::Amp
| TokenKind::Star
| TokenKind::LParen
| TokenKind::LBracket => true,
TokenKind::Kw(Keyword::SelfType | Keyword::Dyn | Keyword::Impl) => true,
_ => false,
}
}
pub(crate) fn type_app(p: &mut Parser) -> Result<TypeApp, ParseError> {
let start = p.peek_span().0;
let grouped = p.check(&TokenKind::LParen);
if grouped {
p.advance();
}
let head = p.ident("expected a type name")?;
let mut args = Vec::new();
loop {
if let Some(named) = p.try_ident() {
args.push(named);
continue;
}
if let TokenKind::Label(name) = &p.peek().kind {
let arg = Ident {
text: format!("'{}", name),
span: p.peek_span(),
};
p.advance();
args.push(arg);
continue;
}
break;
}
if grouped {
p.expect(&TokenKind::RParen, "expected `)` to close the type")?;
}
Ok(TypeApp {
head,
args,
span: (start, p.prev_span().1),
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum TypeStop {
Greedy,
Atom,
}
#[allow(unused_imports)]
use TypeStop::*;
pub(crate) fn type_vars(types: &[&str]) -> Vec<String> {
let mut lifetimes: Vec<String> = Vec::new();
let mut vars: Vec<String> = Vec::new();
for text in types {
let chars: Vec<char> = text.chars().collect();
let mut i = 0;
let n = chars.len();
while i < n {
if !is_word(chars[i]) {
i += 1;
continue;
}
let start = i;
while i < n && is_word(chars[i]) {
i += 1;
}
let word: String = chars[start..i].iter().collect();
let is_lifetime = start > 0 && chars[start - 1] == '\'';
let after_colons = start >= 2 && chars[start - 2] == ':' && chars[start - 1] == ':';
let before_colons = chars.get(i) == Some(&':') && chars.get(i + 1) == Some(&':');
if after_colons || before_colons {
continue;
}
if is_lifetime {
if word != "static" && word != "_" {
let named = format!("'{}", word);
if !lifetimes.contains(&named) {
lifetimes.push(named);
}
}
continue;
}
let is_type_word = match word.as_str() {
"dyn" | "impl" | "mut" | "as" => true,
_ => false,
};
let lowercase_head = word.chars().next().is_some_and(char::is_lowercase);
let excluded = !lowercase_head || primitive_type(&word) || word == "self" || is_type_word;
if excluded {
continue;
}
if !vars.contains(&word) {
vars.push(word);
}
}
}
lifetimes.extend(vars);
lifetimes
}
#[cfg(test)]
mod __vlt_tests {
use super::*;
#[test]
fn an_unsigned_equation_takes_its_parameter_names_from_its_binders() {
let sig = signature_unsigned(&[id("a"), id("b")], (0, 0));
assert_eq!(sig.params.len(), 2);
assert_eq!(sig.params[0].name.text, "a");
assert!(
sig.params[0].ty.is_none(),
"the type is the annotate pass's to fill"
);
}
#[test]
fn an_unsigned_signature_has_no_return_type_which_is_what_marks_it_unsigned() {
let sig = signature_unsigned(&[], (0, 0));
assert!(
sig.ret.is_none(),
"`ty = None` already means `self` to the emitter; `ret = None` is the discriminator"
);
assert!(sig.params.is_empty());
}
#[test]
fn an_absent_return_type_reads_as_unit_which_is_why_annotation_must_precede_emission() {
let sig = signature_unsigned(&[], (0, 0));
assert!(
sig.returns_unit(),
"`returns_unit` cannot tell `()` from unknown, and the emitter asks it"
);
}
#[test]
fn pairs_the_spine_with_the_binders() {
let sig = signature_from(
&[ty("i32"), ty("i32"), ty("i32")],
&[id("a"), id("b")],
(0, 0),
)
.expect("should build");
assert_eq!(sig.params.len(), 2);
assert_eq!(sig.params[0].name.text, "a");
assert_eq!(sig.params[0].ty.as_ref().expect("a type").text, "i32");
assert_eq!(sig.ret.expect("a return type").text, "i32");
}
#[test]
fn a_unit_return_is_recorded_and_not_discarded() {
let sig = signature_from(&[ty("()")], &[], (0, 0)).expect("should build");
assert!(sig.params.is_empty());
assert_eq!(sig.ret.as_ref().expect("a return type").text, "()");
assert!(sig.returns_unit());
}
#[test]
fn too_few_binders_is_an_error_naming_both_counts() {
let err = signature_from(&[ty("i32"), ty("i32"), ty("i32")], &[id("a")], (0, 0))
.expect_err("should fail");
assert!(err.message.contains("arity"), "{}", err.message);
assert!(err.message.contains("takes 2"), "{}", err.message);
assert!(err.message.contains("binds 1"), "{}", err.message);
}
#[test]
fn too_many_binders_is_also_an_error() {
let err = signature_from(&[ty("i32")], &[id("a")], (0, 0)).expect_err("should fail");
assert!(err.message.contains("binds 1"), "{}", err.message);
assert!(err.message.contains("takes 0"), "{}", err.message);
}
#[test]
fn a_zero_parameter_function_can_still_return_something() {
let sig = signature_from(&[ty("u32")], &[], (0, 0)).expect("should build");
assert!(sig.params.is_empty());
assert_eq!(sig.ret.as_ref().expect("a return type").text, "u32");
assert!(!sig.returns_unit());
}
}