bobascript-parser 0.1.1

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

use ast::{Expr, Stmt};
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<Vec<Box<Stmt>>> for crate::grammar::StmtsParser {
  fn parse_ast(input: &'_ str) -> Result<Vec<Box<Stmt>>> {
    let parser = crate::grammar::StmtsParser::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())
    }
  }
}
impl Parser<Box<Expr>> for crate::grammar::ExprParser {
  fn parse_ast(input: &'_ str) -> Result<Box<Expr>> {
    let parser = crate::grammar::ExprParser::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::{ExprParser, StmtsParser},
    Parser,
  };

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

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

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

    let stmt = StmtsParser::parse_ast("let test = 22.5 * if true {3} else {4};").unwrap();
    assert_eq!(
      &format!("{:?}", stmt),
      r#"[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)))))))))]"#
    );

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

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

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

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

  #[test]
  fn parse_if_expr() {
    let expr = ExprParser::parse_ast(r#"if 3 == "3" {15 + 1; 3}"#).unwrap();
    assert_eq!(
      &format!("{:?}", expr),
      r#"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 = ExprParser::parse_ast("if 3 {3} else if 6 {6}").unwrap();
    assert_eq!(
      &format!("{:?}", expr),
      "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 = ExprParser::parse_ast("while true {15 + 1;}").unwrap();
    assert_eq!(
      &format!("{:?}", expr),
      "While(Constant(True), [Expression(Binary(Constant(Number(15.0)), Add, Constant(Number(1.0))))])"
    );
  }

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

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

  #[test]
  fn parse_binary_expr() {
    let expr = ExprParser::parse_ast("22.5 * -44 + 66").unwrap();
    assert_eq!(
      &format!("{:?}", expr),
      "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 = ExprParser::parse_ast("test(3 * 5, 4,)").unwrap();
    assert_eq!(
      &format!("{:?}", expr),
      r#"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 = ExprParser::parse_ast(r#"#[1, 3, 5, #["test", "I hope this works!!"]]"#).unwrap();
    assert_eq!(
      &format!("{:?}", expr),
      r#"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 =
      ExprParser::parse_ast(r#"#[1, 3, 5, #["test", "I hope this works!!"]][3][1]"#).unwrap();
    assert_eq!(
      &format!("{:?}", expr),
      r#"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)))"#
    );
  }
}