voltaic-parser 0.2.0

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

use crate::block::reaches_a_fat_arrow;

use crate::expr::parse_expr;

use crate::item::visibility;

use crate::item::walk_item_for_tails;

use crate::operand::MethodContext;

use crate::operand::receiver;

use crate::parser::Parser;

use crate::signature::TypeStop;

use crate::signature::capture_type;

use crate::signature::type_app;

use crate::token::substitute_var;

use crate::token::token_text;

use voltaic_ast::ClassDecl;

use voltaic_ast::ClassMember;

use voltaic_ast::Constraint;

use voltaic_ast::Ident;

use voltaic_ast::InstanceDecl;

use voltaic_ast::Item;

use voltaic_ast::TypeApp;

use voltaic_ast::Visibility;

use voltaic_diag::Code;

use voltaic_lexer::Keyword;

use voltaic_lexer::TokenKind;

use voltaic_lexer::Trivia;

pub(crate) fn class_body(
  p: &mut Parser,
  ctx: &MethodContext,
) -> Result<Vec<ClassMember>, ParseError> {
  let mut members = Vec::new();
  let opens =
    p.check(&TokenKind::Newline) && p.peek_at(1).is_some_and(|t| t.kind == TokenKind::Indent);
  if !opens {
    return Ok(members);
  }
  p.advance();
  p.advance();
  loop {
    p.skip_separators();
    if p.check(&TokenKind::Dedent) || p.at_eof() {
      break;
    }
    members.push(class_member(p, ctx)?);
  }
  p.expect(&TokenKind::Dedent, "expected the body to end")?;
  Ok(members)
}

pub(crate) fn class_decl(
  p: &mut Parser,
  leading: Vec<Trivia>,
  start: usize,
  visibility: Visibility,
) -> Result<ClassDecl, ParseError> {
  p.expect(&TokenKind::Kw(Keyword::Class), "expected `class`")?;
  let supers = class_supers(p)?;
  let name = p.ident("expected the class's name")?;
  let mut params = Vec::new();
  while let Some(param) = p.try_ident() {
    params.push(param);
  }
  let mut ctx = MethodContext::for_class(&params);
  ctx.recording = Some(name.text.clone());
  let members = class_body(p, &ctx)?;
  Ok(ClassDecl {
    name,
    params,
    supers,
    members,
    visibility,
    span: (start, p.prev_span().1),
    leading,
  })
}

pub(crate) fn class_member(p: &mut Parser, ctx: &MethodContext) -> Result<ClassMember, ParseError> {
  let leading = p.peek_leading();
  let start = p.peek_span().0;
  let member_visibility = visibility(p)?;
  if p.check(&TokenKind::Kw(Keyword::Type)) {
    p.advance();
    let assoc = p.ident("expected a name after `type`")?;
    p.newline_with_trailing("expected a line break after the associated type")?;
    return Ok(ClassMember::AssocType(assoc));
  }
  if p.check(&TokenKind::Kw(Keyword::Const)) {
    p.advance();
    let assoc = p.ident("expected a name after `const`")?;
    p.expect(&TokenKind::Colon, "expected `:` after the constant's name")?;
    let ty = capture_type(p, "expected the constant's type", TypeStop::Greedy)?;
    let value = if p.check(&TokenKind::Eq) {
      p.advance();
      Some(parse_expr(p, 0)?)
    } else {
      None
    };
    p.newline_with_trailing("expected a line break after the associated constant")?;
    return Ok(ClassMember::AssocConst {
      name: assoc,
      ty,
      value,
    });
  }
  let reserved = ctx.reserved();
  let TokenKind::Ident(peeked) = &p.peek().kind else {
    return Err(ParseError::r#with(
      Code::ExpectedName,
      "expected a member's name".to_string(),
      p.peek_span(),
    ));
  };
  let name = Ident {
    text: peeked.clone(),
    span: p.peek_span(),
  };
  let has_signature = p.peek_at(1).is_some_and(|t| t.kind == TokenKind::Colon);
  let head = if has_signature {
    let declared = p.ident("expected a member's name")?;
    let stated = p.signature_head(&declared)?;
    if let Some(recording_class) = &ctx.recording {
      p.record_head(recording_class, &declared.text, &stated);
    }
    stated
  } else {
    let borrowed = ctx
      .implementing
      .as_deref()
      .and_then(|implemented| p.borrowed_head(implemented, &name.text));
    let Some(found) = borrowed else {
      let taken = p.peeked_name("expected a member's name")?;
      let mut inferred = p.unsigned_equations(taken, leading, start, &reserved, false)?;
      inferred.visibility = member_visibility;
      receiver(&mut inferred.sig);
      return Ok(ClassMember::Method(inferred));
    };
    found
  };
  let mut decl = p.equations(name, leading, start, &head, &reserved, true)?;
  decl.visibility = member_visibility;
  receiver(&mut decl.sig);
  for pair in &ctx.vars {
    substitute_var(&mut decl.sig, &pair.0, &pair.1);
  }
  Ok(ClassMember::Method(decl))
}

pub(crate) fn class_supers(p: &mut Parser) -> Result<Vec<Ident>, ParseError> {
  let mark = p.position();
  let mut found = Vec::new();
  let parenthesised = p.check(&TokenKind::LParen);
  if parenthesised {
    p.advance();
  }
  while let Some(name) = p.try_ident() {
    found.push(name);
    if !p.check(&TokenKind::Comma) {
      break;
    }
    p.advance();
  }
  if parenthesised && p.check(&TokenKind::RParen) {
    p.advance();
  }
  if found.is_empty() || !p.check(&TokenKind::FatArrow) {
    p.rewind(mark);
    return Ok(Vec::new());
  }
  p.advance();
  Ok(found)
}

pub(crate) fn constraints(p: &mut Parser) -> Result<Vec<Constraint>, ParseError> {
  let mark = p.position();
  let mut found = Vec::new();
  let parenthesised = p.check(&TokenKind::LParen);
  if parenthesised {
    p.advance();
  }
  loop {
    let start = p.peek_span().0;
    let Some(trait_name) = p.try_ident() else {
      break;
    };
    let Some(var) = p.try_ident() else {
      break;
    };
    found.push(Constraint {
      trait_name,
      var,
      span: (start, p.prev_span().1),
    });
    if !p.check(&TokenKind::Comma) {
      break;
    }
    p.advance();
  }
  if parenthesised && p.check(&TokenKind::RParen) {
    p.advance();
  }
  if found.is_empty() || !p.check(&TokenKind::FatArrow) {
    p.rewind(mark);
    let Some(span) = reaches_a_fat_arrow(p) else {
      return Ok(Vec::new());
    };
    let mut scanning = true;
    while scanning {
      match &p.peek().kind {
        TokenKind::Newline | TokenKind::Indent | TokenKind::Dedent | TokenKind::Eof => {
          scanning = false;
        }
        _ => p.advance(),
      };
    }
    return Err(ParseError::with_help(
      Code::BoundClauseShape,
      "a `=>` here is a bound clause, and this is not one",
      "doc 18 §2.2 reads a bound as a trait and the variable it bounds, each a bare name: `Ord t =>`, or `Ord t, Clone t =>`. A trait with type arguments (`Into<String> t`), a path on the left (`Clone i::IntoIter`), a `?Sized`, and a bound between two lifetimes are all out of reach — `docs/GRAMMAR.md` grades them, and inline Rust is the hatch",
      span,
    ));
  }
  p.advance();
  Ok(found)
}

pub(crate) fn given_clause(p: &mut Parser) -> Result<Option<String>, ParseError> {
  if !p.check(&TokenKind::Kw(Keyword::Given)) {
    return Ok(None);
  }
  let opened = p.peek_span();
  p.advance();
  if !p.check(&TokenKind::Lt) {
    return Err(ParseError::with_help(
      Code::BoundClauseShape,
      "expected `<` after `given`",
      "`given` carries Rust's own generic parameter list, verbatim and in angle brackets: `given <const N: usize>`, `given <T: Display, I: IntoIterator<Item = T>>`",
      p.peek_span(),
    ));
  }
  let mut depth: i32 = 0;
  let mut text = String::new();
  loop {
    let kind = &p.peek().kind;
    let at_end = match kind {
      TokenKind::Newline | TokenKind::Indent | TokenKind::Dedent | TokenKind::Eof => true,
      _ => false,
    };
    if at_end {
      return Err(ParseError::with_help(
        Code::BoundClauseShape,
        "this `given` clause is not closed",
        "a `given` clause runs from `<` to the `>` that matches it, on one line, and the spine follows it",
        opened,
      ));
    }
    match kind {
      TokenKind::Lt => depth += 1,
      TokenKind::Gt => depth -= 1,
      TokenKind::Shr => depth -= 2,
      TokenKind::Compose => depth -= 3,
      _ => (),
    };
    if p.peek().ws_before && !text.is_empty() {
      text.push(' ');
    }
    text.push_str(&token_text(kind));
    p.advance();
    if depth <= 0 {
      break;
    }
  }
  Ok(Some(text))
}

pub(crate) fn instance_decl(
  p: &mut Parser,
  leading: Vec<Trivia>,
  start: usize,
) -> Result<InstanceDecl, ParseError> {
  p.expect(&TokenKind::Kw(Keyword::Instance), "expected `instance`")?;
  let binder = given_clause(p)?;
  let limits = if binder.is_some() {
    Vec::new()
  } else {
    constraints(p)?
  };
  let grouped = p.check(&TokenKind::LParen);
  let first = type_app(p)?;
  let split = if grouped {
    (None, first)
  } else if p.check(&TokenKind::LParen) {
    (Some(first), type_app(p)?)
  } else if first.args.is_empty() {
    (None, first)
  } else {
    let mut args = first.args.clone();
    let head = args.pop().expect("the argument list is not empty");
    let class_end = args.last().map_or(first.head.span.1, |a| a.span.1);
    let class_app = TypeApp {
      head: first.head.clone(),
      args,
      span: (first.head.span.0, class_end),
    };
    let target_app = TypeApp {
      head: head.clone(),
      args: Vec::new(),
      span: head.span,
    };
    (Some(class_app), target_app)
  };
  let implemented = split.0;
  let target = split.1;
  let var = target.vars().first().map(|v| v.to_string());
  let subject = var.as_deref().map_or_else(String::new, TypeApp::rust_name);
  let mut ctx = MethodContext::for_instance(var.as_deref(), &subject);
  ctx.implementing = implemented.as_ref().map(|c| c.head.text.clone());
  let members = class_body(p, &ctx)?;
  Ok(InstanceDecl {
    r#class: implemented,
    target,
    constraints: limits,
    r#given: binder,
    members,
    span: (start, p.prev_span().1),
    leading,
  })
}

pub(crate) fn walk_member_for_tails(member: &ClassMember, problems: &mut Vec<ParseError>) {
  if let ClassMember::Method(decl) = member {
    walk_item_for_tails(&Item::Fn(decl.clone()), problems);
  }
  ();
}