bobascript-parser 0.1.4

A parser for the scripting language BobaScript.
Documentation
use std::{convert::From, fmt::Display, string::String};

use ast::Ast;
use lalrpop_util::{lalrpop_mod, ParseError};
use thiserror::Error;

pub mod ast;
lalrpop_mod!(#[allow(clippy::all)] pub grammar);

#[derive(Debug, Error, Clone)]
pub enum SyntaxError {
  #[error("{0}")]
  Generic(String),
  #[error("Expected {0}.")]
  Expected(String),
  #[error("Expected {0}; found token '{1}'.")]
  UnexpectedToken(String, String),
  #[error("Found extra token {0}.")]
  ExtraToken(String),
  #[error("Invalid token.")]
  Invalid,
}
type Result<T> = std::result::Result<T, SyntaxError>;

impl<T1, T2> From<ParseError<usize, T1, T2>> for SyntaxError
where
  T1: Display,
  T2: Into<String>,
{
  fn from(error: ParseError<usize, T1, T2>) -> Self {
    match error {
      ParseError::InvalidToken { location: _ } => SyntaxError::Invalid,
      ParseError::UnrecognizedEOF {
        location: _,
        expected,
      } => SyntaxError::Expected(expected.join(", ")),
      ParseError::UnrecognizedToken { token, expected } => {
        SyntaxError::UnexpectedToken(expected.join(", "), token.1.to_string())
      }
      ParseError::ExtraToken { token } => SyntaxError::ExtraToken(token.1.to_string()),
      ParseError::User { error } => SyntaxError::Generic(error.into()),
    }
  }
}

pub trait Parser<T> {
  fn parse_ast(input: &'_ str) -> Result<T>;
}
impl Parser<Ast> for crate::grammar::AstParser {
  fn parse_ast(input: &'_ str) -> Result<Ast> {
    let parser = crate::grammar::AstParser::new();
    let mut errors = Vec::new();
    let expr = parser.parse(&mut errors, input);

    if errors.is_empty() {
      Ok(expr.unwrap())
    } else {
      Err(errors.pop().unwrap().into())
    }
  }
}

mod tests {
  #![allow(unused_imports)]
  use crate::{ast::Expr, grammar::AstParser, Parser};

  #[test]
  fn parse_function_stmt() {
    let stmt = AstParser::parse_ast("fn test() { 3 };").unwrap();
    assert_eq!(
      &format!("{:?}", stmt),
      r#"Ast([Function("test", [], Block([], Some(Constant(Number(3.0)))))], None)"#
    );
    let stmt = AstParser::parse_ast("fn test(t1, t2, t3,) { 3 };").unwrap();
    assert_eq!(
      &format!("{:?}", stmt),
      r#"Ast([Function("test", ["t1", "t2", "t3"], Block([], Some(Constant(Number(3.0)))))], None)"#
    );
  }

  #[test]
  fn parse_declaration_stmt() {
    let stmt = AstParser::parse_ast("const test = 5.2 * 3;").unwrap();
    assert_eq!(
      &format!("{:?}", stmt),
      r#"Ast([Const("test", Binary(Constant(Number(5.2)), Multiply, Constant(Number(3.0))))], None)"#
    );

    let stmt = AstParser::parse_ast("let test = 5.2 * 3;").unwrap();
    assert_eq!(
      &format!("{:?}", stmt),
      r#"Ast([Let("test", Some(Binary(Constant(Number(5.2)), Multiply, Constant(Number(3.0)))))], None)"#
    );

    let stmt = AstParser::parse_ast("let test = 22.5 * if true {3} else {4};").unwrap();
    assert_eq!(
      &format!("{:?}", stmt),
      r#"Ast([Let("test", Some(Binary(Constant(Number(22.5)), Multiply, If(Constant(True), Block([], Some(Constant(Number(3.0)))), Some(Block([], Some(Constant(Number(4.0)))))))))], None)"#
    );

    let stmt = AstParser::parse_ast("let test;").unwrap();
    assert_eq!(&format!("{:?}", stmt), r#"Ast([Let("test", None)], None)"#);
  }

  #[test]
  fn parse_return_stmt() {
    let stmt = AstParser::parse_ast(r#"return "howdy!";"#).unwrap();
    assert_eq!(
      &format!("{:?}", stmt),
      r#"Ast([Return(Some(Constant(String("\"howdy!\""))))], None)"#
    );
  }

  #[test]
  fn parse_expression_stmt() {
    let stmt = AstParser::parse_ast("22.5 * (44 + 66);").unwrap();
    assert_eq!(
      &format!("{:?}", stmt),
      "Ast([Expression(Binary(Constant(Number(22.5)), Multiply, Binary(Constant(Number(44.0)), Add, Constant(Number(66.0)))))], None)"
    );
  }

  #[test]
  fn parse_block_expr() {
    let expr = AstParser::parse_ast("{15 + 1; 3}").unwrap();
    assert_eq!(
      &format!("{:?}", expr),
      "Ast([], Some(Block([Expression(Binary(Constant(Number(15.0)), Add, Constant(Number(1.0))))], Some(Constant(Number(3.0))))))"
    );
  }

  #[test]
  fn parse_if_expr() {
    let expr = AstParser::parse_ast(r#"if 3 == "3" {15 + 1; 3}"#).unwrap();
    assert_eq!(
      &format!("{:?}", expr),
      r#"Ast([], Some(If(Binary(Constant(Number(3.0)), Equal, Constant(String("\"3\""))), Block([Expression(Binary(Constant(Number(15.0)), Add, Constant(Number(1.0))))], Some(Constant(Number(3.0)))), None)))"#
    );

    let expr = AstParser::parse_ast("if 3 {3} else if 6 {6}").unwrap();
    assert_eq!(
      &format!("{:?}", expr),
      "Ast([], Some(If(Constant(Number(3.0)), Block([], Some(Constant(Number(3.0)))), Some(If(Constant(Number(6.0)), Block([], Some(Constant(Number(6.0)))), None)))))"
    );
  }

  #[test]
  fn parse_while_expr() {
    let expr = AstParser::parse_ast("while true {15 + 1;}").unwrap();
    assert_eq!(
      &format!("{:?}", expr),
      "Ast([], Some(While(Constant(True), [Expression(Binary(Constant(Number(15.0)), Add, Constant(Number(1.0))))])))"
    );
  }

  #[test]
  fn parse_log_expr() {
    let expr = AstParser::parse_ast(r#"log(a = "arg")"#).unwrap();
    assert_eq!(
      &format!("{:?}", expr),
      r#"Ast([], Some(Log(Assign(Constant(Ident("a")), Assign, Constant(String("\"arg\""))))))"#
    );
  }

  #[test]
  fn parse_assign_expr() {
    let expr = AstParser::parse_ast("a = 5").unwrap();
    assert_eq!(
      &format!("{:?}", expr),
      r#"Ast([], Some(Assign(Constant(Ident("a")), Assign, Constant(Number(5.0)))))"#
    );
    let expr = AstParser::parse_ast("a *= b = 5").unwrap();
    assert_eq!(
      &format!("{:?}", expr),
      r#"Ast([], Some(Assign(Constant(Ident("a")), MultiplyAssign, Assign(Constant(Ident("b")), Assign, Constant(Number(5.0))))))"#
    );
  }

  #[test]
  fn parse_binary_expr() {
    let expr = AstParser::parse_ast("22.5 * -44 + 66").unwrap();
    assert_eq!(
      &format!("{:?}", expr),
      "Ast([], Some(Binary(Binary(Constant(Number(22.5)), Multiply, Unary(Negate, Constant(Number(44.0)))), Add, Constant(Number(66.0)))))"
    );
  }

  #[test]
  fn parse_call_expr() {
    let expr = AstParser::parse_ast("test(3 * 5, 4,)").unwrap();
    assert_eq!(
      &format!("{:?}", expr),
      r#"Ast([], Some(Call(Constant(Ident("test")), [Binary(Constant(Number(3.0)), Multiply, Constant(Number(5.0))), Constant(Number(4.0))])))"#
    );
  }

  #[test]
  fn parse_complex_tuples() {
    let expr = AstParser::parse_ast(r#"#[1, 3, 5, #["test", "I hope this works!!"]]"#).unwrap();
    assert_eq!(
      &format!("{:?}", expr),
      r#"Ast([], Some(Constant(Tuple([Constant(Number(1.0)), Constant(Number(3.0)), Constant(Number(5.0)), Constant(Tuple([Constant(String("\"test\"")), Constant(String("\"I hope this works!!\""))]))]))))"#
    );

    let expr =
      AstParser::parse_ast(r#"#[1, 3, 5, #["test", "I hope this works!!"]][3][1]"#).unwrap();
    assert_eq!(
      &format!("{:?}", expr),
      r#"Ast([], Some(Index(Index(Constant(Tuple([Constant(Number(1.0)), Constant(Number(3.0)), Constant(Number(5.0)), Constant(Tuple([Constant(String("\"test\"")), Constant(String("\"I hope this works!!\""))]))])), Constant(Number(3.0))), Constant(Number(1.0)))))"#
    );
  }
}