voltaic-parser 0.2.0

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

use voltaic_ast::BinaryOp;

use voltaic_ast::Program;

use voltaic_diag::Code;

use voltaic_diag::Span;

use voltaic_lexer::Keyword;

use voltaic_lexer::LexError;

use voltaic_lexer::Token;

use voltaic_lexer::TokenKind;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseError {
  pub code: Code,
  pub message: String,
  pub span: Span,
  pub help: Option<String>,
}

impl ParseError {
  pub(crate) fn expected(message: impl Into<String>, span: Span) -> Self {
    ParseError {
      code: Code::ExpectedToken,
      message: message.into(),
      span,
      help: None,
    }
  }

  pub(crate) fn r#with(code: Code, message: impl Into<String>, span: Span) -> Self {
    ParseError {
      code,
      message: message.into(),
      span,
      help: None,
    }
  }

  pub(crate) fn with_help(
    code: Code,
    message: impl Into<String>,
    help: impl Into<String>,
    span: Span,
  ) -> Self {
    ParseError {
      code,
      message: message.into(),
      span,
      help: Some(help.into()),
    }
  }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseFailure {
  Lex(LexError),
  Parse(Vec<ParseError>),
}
#[allow(unused_imports)]
use ParseFailure::*;

pub const MAX_NESTING: usize = 16;

pub(crate) fn nesting_depth(toks: &[Token]) -> usize {
  let mut depth: usize = 0;
  let mut deepest: usize = 0;
  for token in toks {
    match token.kind {
      TokenKind::LParen | TokenKind::LBracket | TokenKind::LBrace => {
        depth += 1;
        deepest = deepest.max(depth);
      }
      TokenKind::RParen | TokenKind::RBracket | TokenKind::RBrace => {
        depth = depth.saturating_sub(1);
      }
      _ => (),
    };
  }
  deepest
}

pub(crate) fn above_bitor() -> u8 {
  let found = binary_power(&TokenKind::Or).expect("`|` is in the precedence table");
  found.1 + 1
}

pub(crate) fn binary_power(kind: &TokenKind) -> Option<(BinaryOp, u8, u8)> {
  let op = match kind {
    TokenKind::Pipe => BinaryOp::Pipeline,
    TokenKind::Coalesce => BinaryOp::Coalesce,
    TokenKind::Kw(Keyword::Catch) => BinaryOp::Catch,
    TokenKind::DotDot => BinaryOp::Range,
    TokenKind::DotDotEq => BinaryOp::RangeInclusive,
    TokenKind::OrOr => BinaryOp::Or,
    TokenKind::AndAnd => BinaryOp::And,
    TokenKind::EqEq => BinaryOp::Eq,
    TokenKind::Ne => BinaryOp::Ne,
    TokenKind::Lt => BinaryOp::Lt,
    TokenKind::Gt => BinaryOp::Gt,
    TokenKind::Le => BinaryOp::Le,
    TokenKind::Ge => BinaryOp::Ge,
    TokenKind::Or => BinaryOp::BitOr,
    TokenKind::Caret => BinaryOp::BitXor,
    TokenKind::Amp => BinaryOp::BitAnd,
    TokenKind::Shl => BinaryOp::Shl,
    TokenKind::Shr => BinaryOp::Shr,
    TokenKind::Plus => BinaryOp::Add,
    TokenKind::Minus => BinaryOp::Sub,
    TokenKind::Star => BinaryOp::Mul,
    TokenKind::Slash => BinaryOp::Div,
    TokenKind::Percent => BinaryOp::Rem,
    TokenKind::Compose => BinaryOp::Compose,
    TokenKind::ComposeBack => BinaryOp::ComposeBack,
    _ => {
      return None;
    }
  };
  let left = op.power();
  Some((op, left, left + 1))
}

#[cfg(test)]
fn power(kind: TokenKind) -> u8 {
  binary_power(&kind).expect("should have a precedence").1
}

pub(crate) const PREFIX_POWER: u8 = 25;

pub fn parse_source(src: &str) -> Result<Program, ParseFailure> {
  let toks = voltaic_lexer::lex(src).map_err(ParseFailure::Lex)?;
  parse(&toks, src.len()).map_err(ParseFailure::Parse)
}

#[cfg(test)]
mod __vlt_tests {
  use super::*;

  #[test]
  fn precedence_follows_rust() {
    assert!(power(TokenKind::Star) > power(TokenKind::Plus));
    assert!(power(TokenKind::Plus) > power(TokenKind::Shl));
    assert!(power(TokenKind::Shl) > power(TokenKind::Amp));
    assert!(power(TokenKind::Amp) > power(TokenKind::Caret));
    assert!(power(TokenKind::Caret) > power(TokenKind::Or));
    assert!(power(TokenKind::Or) > power(TokenKind::EqEq));
    assert!(power(TokenKind::EqEq) > power(TokenKind::AndAnd));
    assert!(power(TokenKind::AndAnd) > power(TokenKind::OrOr));
  }

  #[test]
  fn the_new_operators_sit_where_doc_03_places_them() {
    assert!(power(TokenKind::Coalesce) < power(TokenKind::OrOr));
    assert!(power(TokenKind::Pipe) < power(TokenKind::Coalesce));
  }

  #[test]
  fn unary_and_postfix_bind_tighter_than_any_binary() {
    assert!(crate::support::PREFIX_POWER > power(TokenKind::Star));
  }

  #[test]
  fn binary_operators_are_left_associative() {
    for kind in [TokenKind::Plus, TokenKind::Star, TokenKind::Pipe] {
      let rung = binary_power(&kind).expect("should have a precedence");
      assert_eq!(rung.2, rung.1 + 1, "{:?} should be left-associative", kind);
    }
  }

  #[test]
  fn a_non_operator_has_no_precedence() {
    assert!(binary_power(&TokenKind::Ident("x".into())).is_none());
    assert!(binary_power(&TokenKind::Newline).is_none());
  }

  #[test]
  fn the_powers_come_from_the_operator() {
    for kind in [
      TokenKind::Pipe,
      TokenKind::Coalesce,
      TokenKind::OrOr,
      TokenKind::EqEq,
      TokenKind::Plus,
      TokenKind::Percent,
    ] {
      let rung = binary_power(&kind).expect("should have a precedence");
      assert_eq!(
        rung.1,
        rung.0.power(),
        "{:?} reports a power of its own",
        kind
      );
      assert_eq!(rung.2, rung.0.power() + 1);
    }
  }

  #[test]
  fn maps_tokens_to_their_operators() {
    assert_eq!(
      binary_power(&TokenKind::Plus).map(|pair| pair.0),
      Some(BinaryOp::Add)
    );
    assert_eq!(
      binary_power(&TokenKind::DotDot).map(|pair| pair.0),
      Some(BinaryOp::Range),
      "`..` is exclusive (doc 03 ยง4.2)"
    );
  }
}