voltaic-parser 0.2.0

Hand-written recursive-descent parser for the Voltaic language.
Documentation
import crate::ParseError
import crate::operand::constructor_path
import crate::operand::range_or_literal
import crate::parser::Parser
import crate::token::token_text
import voltaic_ast::Ident
import voltaic_ast::Pattern
import voltaic_ast::RecordPatternField
import voltaic_lexer::Keyword
import voltaic_lexer::TokenKind

export(crate) pattern : &mut Parser -> Result<Pattern, ParseError>
pattern p =
  span = .peek_span p
  case &(.peek p).kind of
    TokenKind::Ident text | text == "_" ->
      .advance p
      Ok $ Pattern::Wildcard span
    TokenKind::Ident _ ->
      name = (.ident "expected a pattern" p)?
      if .check (&TokenKind::At) p
        .advance p
        if .check (&TokenKind::DotDot) p
          .advance p
          return Ok (Pattern::NamedRest { name, span = (span.0, (.prev_span p).1) })
        inner = (pattern_app p)?
        return Ok (Pattern::As { name, inner = Box::new inner, span = (span.0, (.prev_span p).1) })
      if .check (&TokenKind::LBrace) p
        return record_pattern p name.text span.0
      Ok $ Pattern::Binding name
    TokenKind::DotDot ->
      .advance p
      Ok $ Pattern::Rest span
    TokenKind::LBracket ->
      .advance p
      items = (pattern_series p (&TokenKind::RBracket))?
      (.expect (&TokenKind::RBracket) "expected `]` to close the slice pattern" p)?
      Ok (Pattern::Slice { items, span = (span.0, (.prev_span p).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 ->
      .advance p
      if .check (&TokenKind::RParen) p
        .advance p
        return Ok (Pattern::Tuple { items = Vec::new (), span = (span.0, (.prev_span p).1) })
      inner = (pattern_alt p)?
      if .check (&TokenKind::Comma) p
        .advance p
        mut items = vec! \-> inner
        .extend (pattern_series p (&TokenKind::RParen))? items
        (.expect (&TokenKind::RParen) "expected `)` to close the tuple pattern" p)?
        return Ok (Pattern::Tuple { items, span = (span.0, (.prev_span p).1) })
      (.expect (&TokenKind::RParen) "expected `)` to close a parenthesised pattern" p)?
      Ok inner
    TokenKind::Kw (Keyword::SelfValue) ->
      .advance p
      Ok $ Pattern::Binding $ Ident { text = .to_string "self", span }
    TokenKind::Int _ or TokenKind::Float _ or TokenKind::Char _ or TokenKind::Byte _ or TokenKind::Kw (Keyword::True or Keyword::False) ->
      text = token_text $ &(.peek p).kind
      .advance p
      Ok $ range_or_literal p text span
    TokenKind::Str { raw, .. } ->
      text = .clone raw
      .advance p
      Ok (Pattern::Literal { text, span })
    _ ->
      Err $ ParseError::r#with voltaic_diag::Code::ExpectedName "expected a parameter name, a literal pattern, a range, `_`, `(`, `[`, or `=`" span

export(crate) pattern_alt : &mut Parser -> Result<Pattern, ParseError>
pattern_alt p =
  first = (pattern_app p)?
  if !.check (&TokenKind::Kw Keyword::Or) p
    return Ok first
  mut alts = vec! \-> first
  while .eat_keyword Keyword::Or p
    .push (pattern_app p)? alts
  Ok $ Pattern::Or alts

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

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

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

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

export(crate) starts_a_pattern_argument : &Parser -> bool
starts_a_pattern_argument p = case &(.peek p).kind of
  TokenKind::Ident _ or TokenKind::Int _ or TokenKind::Float _ or TokenKind::Char _ or TokenKind::Byte _ or TokenKind::Str { .. } or TokenKind::LParen -> true
  TokenKind::Kw (Keyword::True) or TokenKind::Kw (Keyword::False) or TokenKind::Kw (Keyword::SelfValue) -> true
  _ -> false