wander 0.5.0

The Wander scripting language.
Documentation
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use logos::{Lexer, Logos};
use serde::Serialize;

use crate::{environment::Environment, HostType, WanderError, identifier::Identifier};

#[derive(Logos, Debug, PartialEq, Clone, Serialize)]
#[logos()]
pub enum Token {
    #[regex("[ \t\n\r]+", ws)]
    WS(String),

    #[token("let")]
    Let,

    #[token("val")]
    Val,

    #[token("in")]
    In,

    #[token("end")]
    End,

    #[token("=")]
    EqualSign,

    #[token("if")]
    If,

    #[token("then")]
    Then,

    #[token("else")]
    Else,

    #[token(".")]
    Period,

    #[token(":")]
    Colon,

    #[token("::")]
    DoubleColon,

    #[token("'")]
    SingleQuote,

    #[regex("-?[0-9]+", int, priority=2)]
    Int(i64),

    #[regex(r#""(([^\x00-\x1F"\\]|\\["\\/bfnrt]|\\u[0-9a-fA-F]{4})*)""#, string)]
    String(String),

    #[regex("[_a-zA-Z]+[_a-zA-Z0-9.?]*", name, priority=2)]
    Name(String),

    #[regex("(true)|(false)", bool)]
    Boolean(bool),

    #[token("(")]
    OpenParen,

    #[token(")")]
    CloseParen,

    #[token("{")]
    OpenBrace,

    #[token("}")]
    CloseBrace,

    #[token("[")]
    OpenSquare,

    #[token("]")]
    CloseSquare,

    #[token("<")]
    OpenAngle,

    #[token(">")]
    CloseAngle,

    #[regex("<[a-zA-Z0-9-._~:/?#\\[\\]@!$&'()*+,;%=\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}\\x{10000}-\\x{1FFFD}\\x{20000}-\\x{2FFFD}\\x{30000}-\\x{3FFFD}\\x{40000}-\\x{4FFFD}\\x{50000}-\\x{5FFFD}\\x{60000}-\\x{6FFFD}\\x{70000}-\\x{7FFFD}\\x{80000}-\\x{8FFFD}\\x{90000}-\\x{9FFFD}\\x{A0000}-\\x{AFFFD}\\x{B0000}-\\x{BFFFD}\\x{C0000}-\\x{CFFFD}\\x{D0000}-\\x{DFFFD}\\x{E1000}-\\x{EFFFD}]+>", identifier)]
    Identifier(Identifier),

    #[token("|")]
    Pipe,

    #[token("->")]
    Arrow,

    #[token("nothing")]
    Nothing,

    #[token("?")]
    QuestionMark,

    #[token("`")]
    Backtick,

    #[token("#")]
    Hash,

    #[regex("--.*\n?", comment)]
    Comment(String),

    #[token("\\")]
    Lambda,

    #[token("fun")]
    Fun,
}

fn bool(lex: &mut Lexer<Token>) -> Option<bool> {
    let slice = lex.slice();
    match slice {
        "true" => Some(true),
        "false" => Some(false),
        _ => None,
    }
}

fn int(lex: &mut Lexer<Token>) -> Option<i64> {
    let slice = lex.slice();
    match slice.parse::<i64>() {
        Ok(value) => Some(value),
        _ => None,
    }
}

fn trim_string(value: &str) -> &str {
    let mut chars = value.chars();
    assert_eq!(chars.next().unwrap(), '"');
    assert_eq!(chars.next_back().unwrap(), '"');
    chars.as_str()
}

fn string(lex: &mut Lexer<Token>) -> Option<String> {
    Some(trim_string(lex.slice()).to_string())
}

fn name(lex: &mut Lexer<Token>) -> Option<String> {
    Some(lex.slice().to_string())
}

fn identifier(lex: &mut Lexer<Token>) -> Option<Identifier> {
    match Identifier::new(lex.slice().trim_start_matches("<").trim_end_matches(">")) {
        Ok(identifier) => Some(identifier),
        Err(_) => None,
    }
}

fn comment(lex: &mut Lexer<Token>) -> Option<String> {
    Some(lex.slice().to_string())
}

fn ws(lex: &mut Lexer<Token>) -> Option<String> {
    Some(lex.slice().to_string())
}

pub fn tokenize(script: &str) -> Result<Vec<Token>, WanderError> {
    let lexer = Token::lexer(script);
    let mut results = vec![];
    for token in lexer {
        match token {
            Ok(token) => results.push(token),
            Err(_) => return Err(WanderError(String::from("Error tokenizing input."))),
        }
    }
    Ok(results)
}

pub fn tokenize_and_filter(script: &str) -> Result<Vec<Token>, WanderError> {
    let tokens = tokenize(script);
    tokens.map(|mut tokens| {
        tokens.retain(|token| !matches!(token, Token::Comment(_)) && !matches!(token, Token::WS(_)));
        tokens
    })
}

pub fn transform<T: HostType>(
    input: &[Token],
    bindings: &Environment<T>,
) -> Result<Vec<Token>, WanderError> {
    let mut index = 0;
    let mut results = vec![];
    while let Some(token) = input.get(index) {
        if token == &Token::Backtick {
            let mut internal_results = vec![];
            let transformer = match input.get(index - 1) {
                Some(Token::Name(name)) => match bindings.read_token_transformer(name) {
                    Some(transformer) => transformer,
                    None => {
                        return Err(WanderError(format!(
                            "{name} Token Transformer doesn't exist."
                        )))
                    }
                },
                _ => return Err(WanderError("Token Transforms require a name.".to_owned())),
            };
            results.pop(); //remove transformer's name token
            index += 1; //skip first `
            while let Some(token) = input.get(index) {
                if token == &Token::Backtick {
                    let transformed_content = transformer(&internal_results).unwrap();
                    results.append(&mut transformed_content.to_vec());
                    break;
                } else {
                    internal_results.push(token.to_owned());
                }
                index += 1;
            }
        } else {
            results.push(token.to_owned());
        }
        index += 1;
    }
    Ok(results)
}