voltaic-parser 0.2.0

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

use crate::operand::constructor_path;

use crate::operand::range_or_literal;

use crate::parser::Parser;

use crate::token::token_text;

use voltaic_ast::Ident;

use voltaic_ast::Pattern;

use voltaic_ast::RecordPatternField;

use voltaic_lexer::Keyword;

use voltaic_lexer::TokenKind;

pub(crate) fn pattern(p: &mut Parser) -> Result<Pattern, ParseError> {
  let span = p.peek_span();
  match &p.peek().kind {
    TokenKind::Ident(text) if text == "_" => {
      p.advance();
      Ok(Pattern::Wildcard(span))
    }
    TokenKind::Ident(_) => {
      let name = p.ident("expected a pattern")?;
      if p.check(&TokenKind::At) {
        p.advance();
        if p.check(&TokenKind::DotDot) {
          p.advance();
          return Ok(Pattern::NamedRest {
            name,
            span: (span.0, p.prev_span().1),
          });
        }
        let inner = pattern_app(p)?;
        return Ok(Pattern::As {
          name,
          inner: Box::new(inner),
          span: (span.0, p.prev_span().1),
        });
      }
      if p.check(&TokenKind::LBrace) {
        return record_pattern(p, name.text, span.0);
      }
      Ok(Pattern::Binding(name))
    }
    TokenKind::DotDot => {
      p.advance();
      Ok(Pattern::Rest(span))
    }
    TokenKind::LBracket => {
      p.advance();
      let items = pattern_series(p, &TokenKind::RBracket)?;
      p.expect(
        &TokenKind::RBracket,
        "expected `]` to close the slice pattern",
      )?;
      Ok(Pattern::Slice {
        items,
        span: (span.0, p.prev_span().1),
      })
    }
    TokenKind::DotDotDot => Err(ParseError::r#with(
      voltaic_diag::Code::EllipsisRestPattern,
      "`...` left the language with doc 18 §7 — write a rest pattern Rust's way: `..` to discard, or `rest @ ..` to bind",
      span,
    )),
    TokenKind::LParen => {
      p.advance();
      if p.check(&TokenKind::RParen) {
        p.advance();
        return Ok(Pattern::Tuple {
          items: Vec::new(),
          span: (span.0, p.prev_span().1),
        });
      }
      let inner = pattern_alt(p)?;
      if p.check(&TokenKind::Comma) {
        p.advance();
        let mut items = vec![inner];
        items.extend(pattern_series(p, &TokenKind::RParen)?);
        p.expect(
          &TokenKind::RParen,
          "expected `)` to close the tuple pattern",
        )?;
        return Ok(Pattern::Tuple {
          items,
          span: (span.0, p.prev_span().1),
        });
      }
      p.expect(
        &TokenKind::RParen,
        "expected `)` to close a parenthesised pattern",
      )?;
      Ok(inner)
    }
    TokenKind::Kw(Keyword::SelfValue) => {
      p.advance();
      Ok(Pattern::Binding(Ident {
        text: "self".to_string(),
        span,
      }))
    }
    TokenKind::Int(_)
    | TokenKind::Float(_)
    | TokenKind::Char(_)
    | TokenKind::Byte(_)
    | TokenKind::Kw(Keyword::True | Keyword::False) => {
      let text = token_text(&p.peek().kind);
      p.advance();
      Ok(range_or_literal(p, text, span))
    }
    TokenKind::Str { raw, .. } => {
      let text = raw.clone();
      p.advance();
      Ok(Pattern::Literal { text, span })
    }
    _ => Err(ParseError::r#with(
      voltaic_diag::Code::ExpectedName,
      "expected a parameter name, a literal pattern, a range, `_`, `(`, `[`, or `=`",
      span,
    )),
  }
}

pub(crate) fn pattern_alt(p: &mut Parser) -> Result<Pattern, ParseError> {
  let first = pattern_app(p)?;
  if !p.check(&TokenKind::Kw(Keyword::Or)) {
    return Ok(first);
  }
  let mut alts = vec![first];
  while p.eat_keyword(Keyword::Or) {
    alts.push(pattern_app(p)?);
  }
  Ok(Pattern::Or(alts))
}

pub(crate) fn pattern_app(p: &mut Parser) -> Result<Pattern, ParseError> {
  let start = p.peek_span().0;
  let Some(path) = constructor_path(p) else {
    return pattern(p);
  };
  if p.check(&TokenKind::LBrace) {
    return record_pattern(p, path, start);
  }
  let mut args = Vec::new();
  while starts_a_pattern_argument(p) {
    args.push(pattern(p)?);
  }
  if args.is_empty() {
    return Ok(Pattern::Binding(Ident {
      text: path,
      span: (start, p.prev_span().1),
    }));
  }
  Ok(Pattern::Apply {
    path,
    args,
    span: (start, p.prev_span().1),
  })
}

pub(crate) fn pattern_series(
  p: &mut Parser,
  close: &TokenKind,
) -> Result<Vec<Pattern>, ParseError> {
  let mut items = Vec::new();
  p.skip_layout();
  while !p.check(close) {
    items.push(pattern_alt(p)?);
    p.skip_layout();
    if !p.check(&TokenKind::Comma) {
      break;
    }
    p.advance();
    p.skip_layout();
  }
  Ok(items)
}

pub(crate) fn patterns(p: &mut Parser) -> Result<Vec<Pattern>, ParseError> {
  let mut found = Vec::new();
  while !p.at_eof() && !p.check(&TokenKind::Eq) && !p.check(&TokenKind::Newline) {
    found.push(pattern(p)?);
  }
  Ok(found)
}

pub(crate) fn record_pattern(
  p: &mut Parser,
  head: String,
  start: usize,
) -> Result<Pattern, ParseError> {
  let mut path = head;
  while p.check(&TokenKind::ColonColon) {
    p.advance();
    let segment = p.ident("expected a name after `::`")?;
    path.push_str("::");
    path.push_str(&segment.text);
  }
  p.expect(&TokenKind::LBrace, "expected `{` to open a record pattern")?;
  let mut fields = Vec::new();
  let mut rest = false;
  p.skip_layout();
  while !p.check(&TokenKind::RBrace) {
    if p.check(&TokenKind::DotDot) {
      p.advance();
      rest = true;
      break;
    }
    let name = p.ident("expected a field name in a record pattern")?;
    let bound = if p.check(&TokenKind::Eq) {
      p.advance();
      Some(pattern_alt(p)?)
    } else {
      None
    };
    fields.push(RecordPatternField {
      name,
      pattern: bound,
    });
    p.skip_layout();
    if !p.check(&TokenKind::Comma) {
      break;
    }
    p.advance();
    p.skip_layout();
  }
  p.expect(
    &TokenKind::RBrace,
    "expected `}` to close the record pattern",
  )?;
  Ok(Pattern::Record {
    path,
    fields,
    rest,
    span: (start, p.prev_span().1),
  })
}

pub(crate) fn starts_a_pattern_argument(p: &Parser) -> bool {
  match &p.peek().kind {
    TokenKind::Ident(_)
    | TokenKind::Int(_)
    | TokenKind::Float(_)
    | TokenKind::Char(_)
    | TokenKind::Byte(_)
    | TokenKind::Str { .. }
    | TokenKind::LParen => true,
    TokenKind::Kw(Keyword::True)
    | TokenKind::Kw(Keyword::False)
    | TokenKind::Kw(Keyword::SelfValue) => true,
    _ => false,
  }
}