avid 0.6.1

A plug-and-play scripting language
Documentation
#![cfg(test)]
use std::collections::{HashMap, HashSet};

use crate::{
    errors::Error,
    func::{AvidFunc, Builtin},
    parser::{Lexer, Parser, TokenData},
    stack::Stack,
    typing::{Object, ObjectType},
    Avid, Builder, ErrorKind, Location, Ast,
};

fn call_ops<T: AvidFunc>(ops: &mut [T]) -> crate::Result<Stack, ErrorKind> {
    let mut stack = Stack::new();

    for op in ops {
        op.call(&mut stack)?;
    }
    Ok(stack)
}

#[test]
fn add() {
    let mut ops = [Builtin::PushInt(34), Builtin::PushInt(35), Builtin::Sum];

    let mut stack = call_ops(&mut ops).unwrap();

    assert_eq!(Object::Num(69), stack.pop::<1>().unwrap()[0])
}

#[test]
fn subtract() {
    let mut ops = [
        Builtin::PushInt(34),
        Builtin::PushInt(35),
        Builtin::Subtract,
    ];

    let mut stack = call_ops(&mut ops).unwrap();

    assert_eq!(Object::Num(-1), stack.pop::<1>().unwrap()[0])
}

#[test]
fn not_enough_on_stack() {
    let mut ops = [Builtin::Print];

    let res = call_ops(&mut ops);

    assert_eq!(
        res,
        Err(ErrorKind::NotEnoughArgs {
            expected: 1,
            got: 0
        })
    )
}

#[test]
fn incorrect_types() {
    let mut ops = [
        Builtin::PushInt(5),
        Builtin::PushStr("kfdj".to_string()),
        Builtin::Sum,
    ];

    let res = call_ops(&mut ops);

    assert_eq!(
        res,
        Err(ErrorKind::IncorrectType {
            expected: &[ObjectType::Num, ObjectType::Num],
            got: vec![ObjectType::Num, ObjectType::String]
        })
    )
}

#[test]
fn parses_correctly() {
    let lexer = Lexer::new("1 2 3 - + -fgh -22 \"Hello, World!\" print", None);

    let toks = lexer.map(|tok| tok.unwrap().data).collect::<Vec<_>>();

    let expected = vec![
        TokenData::Int(1),
        TokenData::Int(2),
        TokenData::Int(3),
        TokenData::Promise("-".to_string()),
        TokenData::Promise("+".to_string()),
        TokenData::Promise("-fgh".to_string()),
        TokenData::Int(-22),
        TokenData::String("Hello, World!".to_string()),
        TokenData::Promise("print".to_string()),
    ];

    assert_eq!(toks, expected)
}

#[test]
fn handles_unfinished_string() {
    let mut lexer = Lexer::new("\"hi", None);
    let tok = lexer.next().unwrap();

    assert_eq!(
        tok,
        Err(Error::new(
            ErrorKind::UnclosedString,
            Location {
                line: 1,
                col: 3,
                file_name: None
            }
        ))
    )
}

#[test]
fn handles_incorrect_number() {
    let mut lexer = Lexer::new("2a", None);
    let tok = lexer.next().unwrap().map_err(|x| x.kind);

    assert_eq!(tok, Err(ErrorKind::IncorrectNumber))
}

#[test]
fn runs_correctly() {
    let src = "32 35 + 2 +";

    let avid = Builder::new(src).build().unwrap();

    let mut stack = avid.run(None).unwrap();

    assert_eq!(stack.pop::<1>(), Ok([Object::Num(69)]))
}

#[test]
fn pop_untyped() {
    let mut stack = Stack::new();

    stack.push(Object::Num(1));
    stack.push(Object::Num(2));

    let res = stack.pop::<2>();

    assert_eq!(res, Ok([Object::Num(1), Object::Num(2)]));

    let res = stack.pop::<5>();

    assert_eq!(
        res,
        Err(ErrorKind::NotEnoughArgs {
            expected: 5,
            got: 0
        })
    );
}

#[test]
fn pop_typed() {
    let mut stack = Stack::new();

    stack.push(Object::String("sus".to_string()));
    stack.push(Object::Num(2));

    const EXPECTED_TYPES: [ObjectType; 2] = [ObjectType::Num, ObjectType::Num];

    let res = stack.pop_typed(&EXPECTED_TYPES);

    assert_eq!(
        res,
        Err(ErrorKind::IncorrectType {
            expected: &EXPECTED_TYPES,
            got: vec![ObjectType::String, ObjectType::Num]
        })
    );

    let res = stack.pop::<5>();

    assert_eq!(
        res,
        Err(ErrorKind::NotEnoughArgs {
            expected: 5,
            got: 2
        })
    );
}

#[test]
fn lexer_unclosed_string() {
    let mut lexer = Lexer::new("\"Hi", None);

    let res = lexer.next().map(|x| x.map_err(|e| e.kind));

    assert_eq!(res, Some(Err(ErrorKind::UnclosedString)))
}

#[test]
fn lexer_string() {
    let mut lexer = Lexer::new("\"Hi\"", None);

    let res = lexer.next().map(|x| x.map(|t| t.data));

    assert_eq!(res, Some(Ok(TokenData::String("Hi".to_string()))))
}

#[test]
fn lexer_number() {
    let lexer = Lexer::new("420 -69 0b101 0o11 0xF6 0", None);

    let res: Vec<TokenData> = lexer.map(|r| r.unwrap().data).collect();

    let expected: Vec<TokenData> = vec![420, -69, 5, 9, 246, 0]
        .into_iter()
        .map(TokenData::Int)
        .collect();

    assert_eq!(res, expected);
}

#[test]
fn lexer_bad_number() {
    let mut lexer = Lexer::new("5zgy", None);

    let res = lexer.next().unwrap().unwrap_err().kind;

    assert_eq!(res, ErrorKind::IncorrectNumber);
}

#[test]
fn handles_unknown_var() {
    let src = "amogus";

    let provided = HashMap::new();

    let parser = Parser::<Ast>::new(src, None, HashSet::new(), provided);

    let res = parser.parse().map_err(|e| e.kind).unwrap_err();

    assert_eq!(
        res,
        ErrorKind::UnknownVar {
            name: "amogus".to_string()
        }
    )
}

#[test]
fn handles_unmatched_end() {
    let src = "end";

    let res = Builder::new(src).build().map_err(|e| e.kind);

    assert_eq!(res, Err(ErrorKind::UnpairedEnd))
}

#[test]
fn handles_basic_if() {
    let src = "1 true if 2 end false if 3 end";

    let avid = Builder::new(src).build().unwrap();

    let stack = avid.run(None).unwrap().into_objects();

    assert_eq!(stack, vec![Object::Num(1), Object::Num(2)])
}

#[test]
fn unclosed_if() {
    let src = "true if";

    let res = Builder::new(src).build().map_err(|e| e.kind);

    assert_eq!(res, Err(ErrorKind::UnclosedIf));
}

#[test]
fn parse_into_ast() {
    let src = "push-a print";

    let a = "Hello from the other thread!!".to_string();

    let ast = Builder::new_ast(src)
        .register_fn("push-a", |s: &mut Stack| {
            let b = &a;
            s.push(Object::String(b.clone()));
            Ok(())
        })
        .build()
        .unwrap();

    Avid::from_ast(ast).run(None).unwrap();
}

#[test]
fn test_error_reporting() {
    let src = [
        "+",
        "1 \n\"b\" +",
        "\n\n\njb",
        "unknown",
        "end",
        "if",
        "while\n do\n",
        "while",
        "do",
        "\"",
        "8fjdd",
        "\\j"
    ];

    fn map_fn(line: &str) -> crate::Result<()> {
        Builder::new(line)
            .src_name("example")
            .promise("unknown")
            .build()?
            .run(None)?;
        Ok(())
    }

    let res = src
        .into_iter()
        .map(|line| {
            format!("{}", map_fn(line).unwrap_err())
        })
        .collect::<Vec<_>>();

    let expected = [
        "example:1:1: Error: Not Enough Arguments: expected 2, got 0!\n",
        "example:2:5: Error: Incorrect Argument Type: expected [Num, Num], got [Num, String]!\n",
        "example:4:1: Error: Unknown Variable: \"jb\"!\n",
        "example:1:1: Error: Unfulfilled Promise: \"unknown\"!\nNote: This is ususally the fault of the program interpreting the code. You should file a bug report if possible.\n",
        "example:1:1: Error: `End` Without Start of Block!\n",
        "example:1:1: Error: `if` Statement Without Matching `end`!\n",
        "example:2:2: Error: `while` Statement Without Matching `end`!\n",
        "example:1:1: Error: `while` Statement Without Matching `end`!\n",
        "example:1:1: Error: `while` Statement Without Matching `end`!\n",
        "example:1:1: Error: Unclosed String!\n",
        "example:1:1: Error: Unparseable Number!\n",
        "example:1:1: Error: Unknown Variable: \"\\j\"!\n",
    ].into_iter().map(String::from).collect::<Vec<_>>();

    assert_eq!(res, expected, "\nRes: {:#?}\nExpected: {:#?}", res, expected)
}