blanket-script 0.0.2

BlanketScript is a simple script language inspired by Rust that transpiles to JavaScript.
Documentation
use nom::{
    character::complete::{multispace0, multispace1},
    error::ParseError,
    multi::many0,
    sequence::delimited,
    IResult, Parser,
};
use statement::parse_statement;
use thiserror::Error;

pub(crate) mod expression;
pub(crate) mod statement;

pub use expression::{Associativity, BinaryOperator, Expression, StringLiteral, UnaryOperator};
pub use statement::Statement;

#[derive(Error, Debug, PartialEq)]
pub enum SyntaxErrorKind {
    #[error("Empty input")]
    EmptyInput,
    #[error("Trailing characters")]
    TrailingCharacters,
    #[error("Numeric literals cannot have consecutive underscores")]
    ConsecutiveUnderscoreInNumericLiteral,
    #[error("Numeric literals cannot start with an underscore")]
    NumericLiteralStartsWithUnderscore,
    #[error("Numeric literals cannot end with an underscore")]
    NumericLiteralEndsWithUnderscore,
    #[error("Empty bigint literal")]
    EmptyBigIntLiteral,
    #[error("Invalid hexadecimal digit")]
    InvalidHexDigit,
    #[error("Missing opening brace in unicode code point escape sequence")]
    MissingUnicodeCodePointOpeningBrace,
    #[error("Missing closing brace in unicode code point escape sequence")]
    MissingUnicodeCodePointClosingBrace,
    #[error("Invalid unicode code point: {0}")]
    InvalidUnicodeCodePoint(u32),
    #[error("Nom error: {0:?}")]
    NomError(nom::error::ErrorKind),
}

#[derive(Debug, PartialEq)]
pub struct SyntaxError<'a> {
    pub input: &'a str,
    pub error: SyntaxErrorKind,
}

impl<'a> nom::error::ParseError<&'a str> for SyntaxError<'a> {
    fn from_error_kind(input: &'a str, kind: nom::error::ErrorKind) -> Self {
        SyntaxError {
            input,
            error: match kind {
                nom::error::ErrorKind::Eof => SyntaxErrorKind::EmptyInput,
                _ => SyntaxErrorKind::NomError(kind),
            },
        }
    }

    fn append(_: &'a str, _: nom::error::ErrorKind, other: Self) -> Self {
        other
    }
}

pub type ParseResult<'a, T> = IResult<&'a str, T, SyntaxError<'a>>;

pub fn parse(input: &str) -> ParseResult<Vec<Statement>> {
    if input.is_empty() {
        return Err(nom::Err::Failure(SyntaxError {
            input,
            error: SyntaxErrorKind::EmptyInput,
        }));
    }
    let (remaining, parsed) = many0(ws0(parse_statement)).parse(input)?;
    if remaining.is_empty() {
        return Ok((remaining, parsed));
    }
    Err(nom::Err::Failure(SyntaxError {
        input,
        error: SyntaxErrorKind::TrailingCharacters,
    }))
}

/// Matches a parser surrounded by optional whitespace.
fn ws0<'a, O, E: ParseError<&'a str>, F>(inner: F) -> impl Parser<&'a str, Output = O, Error = E>
where
    F: Parser<&'a str, Output = O, Error = E>,
{
    delimited(multispace0, inner, multispace0)
}

/// Matches a parser surrounded by at least one whitespace on each side.
fn ws1<'a, O, E: ParseError<&'a str>, F>(inner: F) -> impl Parser<&'a str, Output = O, Error = E>
where
    F: Parser<&'a str, Output = O, Error = E>,
{
    delimited(multispace1, inner, multispace1)
}