voltaic-parser 0.1.0

Hand-written recursive-descent parser for the Voltaic language.
Documentation
use crate::ParseError;

use crate::block::stmt;

use crate::parser::Parser;

use crate::signature::can_end_a_type;

use crate::token::token_text;

use voltaic_ast::Expr;

use voltaic_ast::Item;

use voltaic_ast::Pattern;

use voltaic_ast::Stmt;

use voltaic_ast::where_cycle;

use voltaic_diag::Code;

use voltaic_lexer::Keyword;

use voltaic_lexer::TokenKind;

pub(crate) fn where_entry_is_a_function(p: &Parser) -> bool {
  let named = match p.peek().kind {
    TokenKind::Ident(_) => true,
    _ => false,
  };
  if !named {
    return false;
  }
  let Some(next) = p.peek_at(1) else {
    return p.reaches_a_binding_eq();
  };
  match &next.kind {
    TokenKind::Colon => !p.reaches_a_binding_eq(),
    TokenKind::Eq | TokenKind::LBrace => false,
    _ => p.reaches_a_binding_eq(),
  }
}

pub(crate) fn where_group(entries: &[Stmt], params: &[Pattern]) -> Result<(), ParseError> {
  let bound: Vec<String> = params.iter().flat_map(Pattern::binders).collect();
  for entry in entries {
    match &entry.expr {
      Expr::Item { .. } => {
        continue;
      }
      Expr::Binding(b) => {
        for name in b.pattern.binders() {
          if bound.contains(&name) {
            let message = format!(
              "`{}` is already a parameter of this equation, and a `where` entry may not shadow one",
              name
            );
            return Err(ParseError::with_help(
              Code::WhereGroupShape,
              message,
              "a `where` group is sorted into dependency order, so a name that means the parameter in one entry and the local in another would depend on where the sort put them — rename the local",
              b.span,
            ));
          }
        }
      }
      other => {
        return Err(ParseError::with_help(
          Code::WhereGroupShape,
          "a `where` entry defines a name, so it is a binding or a function declaration and nothing else",
          "an expression evaluated for its effect belongs in the body: a `where` group is a set of definitions and is emitted in dependency order, so an entry with an effect has no position to be emitted at",
          other.span(),
        ));
      }
    };
  }
  if let Some(names) = where_cycle(entries) {
    let joined = names.join(", ");
    let message = format!(
      "these `where` bindings depend on each other in a cycle: {}",
      joined
    );
    let blame = entries
      .iter()
      .find(|e| {
        e.where_entry_name()
          .is_some_and(|binder| names.contains(&binder))
      })
      .map_or((0, 0), |e| e.span);
    return Err(ParseError::with_help(
      Code::WhereGroupShape,
      message,
      "Rust `let` bindings are evaluated in order, so a cycle among values has no order to emit. Haskell accepts this and diverges; give one of them a parameter, which makes it a nested function and lets the cycle be recursion",
      blame,
    ));
  }
  Ok(())
}

pub(super) fn where_locals(params: &[Pattern], p: &mut Parser) -> Result<Vec<Stmt>, ParseError> {
  let opens = p.check(&TokenKind::Indent)
    && p
      .peek_at(1)
      .is_some_and(|t| t.kind == TokenKind::Kw(Keyword::Where));
  if !opens {
    return Ok(Vec::new());
  }
  p.advance();
  p.advance();
  p.newline_with_trailing("expected a line break after `where`")?;
  p.expect_with(
    Code::EmptyBody,
    &TokenKind::Indent,
    "expected indented `where` bindings",
  )?;
  let mut locals = Vec::new();
  loop {
    p.skip_separators();
    if p.check(&TokenKind::Dedent) || p.at_eof() {
      break;
    }
    if where_entry_is_a_function(p) {
      let start = p.peek_span().0;
      let decl = p.where_function()?;
      let end = p.prev_span().1;
      let entry = Expr::Item {
        item: Box::new(Item::Fn(decl)),
        span: (start, end),
      };
      locals.push(Stmt {
        expr: entry,
        span: (start, end),
        leading: Vec::new(),
        trailing: None,
      });
      continue;
    }
    locals.push(stmt(p)?);
  }
  p.expect(&TokenKind::Dedent, "expected the `where` bindings to end")?;
  p.skip_separators();
  p.expect(&TokenKind::Dedent, "expected the `where` clause to end")?;
  where_group(&locals, params)?;
  Ok(locals)
}

pub(crate) fn where_predicates(p: &mut Parser) -> Result<Option<String>, ParseError> {
  if !p.check(&TokenKind::Kw(Keyword::Where)) {
    return Ok(None);
  }
  let opened = p.peek_span();
  p.advance();
  let mut text = String::new();
  let mut last = TokenKind::Eof;
  loop {
    let kind = &p.peek().kind;
    let at_end = match kind {
      TokenKind::Newline | TokenKind::Indent | TokenKind::Dedent | TokenKind::Eof => true,
      _ => false,
    };
    if at_end {
      break;
    }
    if p.peek().ws_before && !text.is_empty() {
      text.push(' ');
    }
    text.push_str(&token_text(kind));
    last = kind.clone();
    p.advance();
  }
  if text.is_empty() {
    return Err(ParseError::with_help(
      Code::BoundClauseShape,
      "this `where` carries no predicates",
      "a signature's `where` holds Rust's own predicates, verbatim: `where I::IntoIter: Clone`. Voltaic's other `where` — the one that introduces local definitions — is indented after the body, which is how doc 03 §14.2 tells the two apart",
      opened,
    ));
  }
  if !can_end_a_type(&last) {
    return Err(ParseError::with_help(
      Code::BoundClauseShape,
      "this `where` clause does not end where a type can end",
      "a predicate ends in a trait name or a closing bracket — `where I::IntoIter: Clone`. This one ends mid-expression, so the verbatim text would reach the generated Rust unfinished",
      opened,
    ));
  }
  Ok(Some(text))
}