airlang 0.23.0

Air is a minimalist and universal programming language.
Documentation
use std::error::Error;
use std::str::FromStr;

use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_traits::Num;

use super::parse;
use super::repr::CellRepr;
use super::repr::PairRepr;
use super::repr::Repr;
use crate::test::parse_test_file;
use crate::type_::Bit;
use crate::type_::Call;
use crate::type_::Decimal;
use crate::type_::Int;
use crate::type_::Key;
use crate::type_::Map;
use crate::type_::Pair;
use crate::type_::Text;
use crate::type_::Unit;

fn unit() -> Repr {
    Repr::Unit(Unit)
}

fn bit(b: bool) -> Repr {
    Repr::Bit(Bit::from(b))
}

fn key(s: &str) -> Repr {
    Repr::Key(Key::from_str_unchecked(s))
}

fn text(s: &str) -> Repr {
    Repr::Text(Text::from(s))
}

fn int(s: &str, radix: u8) -> Repr {
    let i = Int::new(BigInt::from_str_radix(s, radix as u32).unwrap());
    Repr::Int(i)
}

fn decimal(sign: bool, exp: i64, significand: &str) -> Repr {
    let i = BigInt::from_str(significand).unwrap();
    let i = if sign { i } else { -i };
    let scale = significand.len() as i64 - 1 - exp;
    let decimal = Decimal::new(BigDecimal::from_bigint(i, scale));
    Repr::Decimal(decimal)
}

fn byte(b: Vec<u8>) -> Repr {
    Repr::Byte(b.into())
}

fn cell(value: Repr) -> Repr {
    Repr::Cell(Box::new(CellRepr::new(value)))
}

fn pair(left: Repr, right: Repr) -> Repr {
    Repr::Pair(Box::new(PairRepr::new(left, right)))
}

fn call(func: Repr, input: Repr) -> Repr {
    let call = Call { func, input };
    Repr::Call(Box::new(call))
}

fn infix_call(left: Repr, middle: Repr, right: Repr) -> Repr {
    let call = Call { func: middle, input: Repr::Pair(Box::new(Pair::new(left, right))) };
    Repr::Call(Box::new(call))
}

fn list(v: Vec<Repr>) -> Repr {
    Repr::List(v.into())
}

fn map(v: Vec<(Key, Repr)>) -> Repr {
    Repr::Map(Map::from_iter(v))
}

fn test_parse(
    src: &str, file_name: &str, expected: impl FnOnce() -> Vec<Repr>,
) -> Result<(), Box<dyn Error>> {
    let mut expected = expected().into_iter();
    let cases = parse_test_file::<2>(src, file_name);
    if expected.len() != cases.len() {
        return Err(format!("file {file_name} length not equal").into());
    }
    for [title, s] in cases {
        let expected_repr = expected.next().unwrap();
        let real_repr: Repr = parse(s).map_err(|e| {
            eprintln!("file {file_name} case ({title}) src({s}): parse failed\n{e}");
            e
        })?;
        assert_eq!(
            real_repr, expected_repr,
            "file {file_name} case ({title}) src({s}): expect({expected_repr}) != real({real_repr})"
        );
    }
    Ok(())
}

fn test_generate(src: &str, file_name: &str) -> Result<(), Box<dyn Error>> {
    for [title, s] in parse_test_file::<2>(src, file_name) {
        let repr: Repr = parse(s).map_err(|e| {
            eprintln!("file {file_name} case ({title}) src({s}): parse failed\n{e}");
            e
        })?;
        let fmt_list = [
            format!("{repr}"),
            format!("{repr:#}"),
            format!("{repr:?}"),
            format!("{repr:#?}"),
            format!("{repr:<}"),
            format!("{repr:<#}"),
            format!("{repr:<?}"),
            format!("{repr:<#?}"),
            format!("{repr:^}"),
            format!("{repr:^#}"),
            format!("{repr:^?}"),
            format!("{repr:^#?}"),
            format!("{repr:>}"),
            format!("{repr:>#}"),
            format!("{repr:>?}"),
            format!("{repr:>#?}"),
        ];
        for repr_str in fmt_list {
            let new_repr = parse(&repr_str).map_err(|e| {
                eprintln!(
                    "file {file_name} case ({title}) src({s}): parse error with generated string ({repr_str})!\n{e}"
                );
                e
            })?;
            assert_eq!(
                repr, new_repr,
                "file {file_name} case ({title}) src({s}): original({repr}) != re-parsed({new_repr})"
            );
        }
    }
    Ok(())
}

fn test_parse_illegal(src: &str, file_name: &str) -> Result<(), Box<dyn Error>> {
    for [title, s] in parse_test_file::<2>(src, file_name) {
        assert!(
            parse::<Repr>(s).is_err(),
            "file {file_name} case ({title}) src({s}): shouldn't parse"
        );
    }
    Ok(())
}

fn test_parse_bad(src: &str, file_name: &str) -> Result<(), Box<dyn Error>> {
    for [title, i1, i2] in parse_test_file::<3>(src, file_name) {
        let i1 = parse::<Repr>(i1).map_err(|e| {
            eprintln!("file {file_name} case ({title}): ({i1}) parse failed\n{e}");
            e
        })?;
        let i2 = parse::<Repr>(i2).map_err(|e| {
            eprintln!("file {file_name} case ({title}): ({i2}) parse failed\n{e}");
            e
        })?;
        assert_eq!(i1, i2, "file {file_name} case ({title}): expect({i2}) != real({i1})");
    }
    Ok(())
}

#[test]
fn test_parse_scope() -> Result<(), Box<dyn Error>> {
    test_parse(include_str!("test/scope.air"), "test/scope.air", scope::expected)
}

#[test]
fn test_generate_scope() -> Result<(), Box<dyn Error>> {
    test_generate(include_str!("test/scope.air"), "test/scope.air")
}

#[test]
fn test_space() -> Result<(), Box<dyn Error>> {
    test_parse(include_str!("test/space.air"), "test/space.air", space::expected)
}

#[test]
fn test_parse_unit() -> Result<(), Box<dyn Error>> {
    test_parse(include_str!("test/unit.air"), "test/unit.air", unit::expected)
}

#[test]
fn test_generate_unit() -> Result<(), Box<dyn Error>> {
    test_generate(include_str!("test/unit.air"), "test/unit.air")
}

#[test]
fn test_parse_bit() -> Result<(), Box<dyn Error>> {
    test_parse(include_str!("test/bit.air"), "test/bit.air", bit::expected)
}

#[test]
fn test_generate_bit() -> Result<(), Box<dyn Error>> {
    test_generate(include_str!("test/bit.air"), "test/bit.air")
}

#[test]
fn test_parse_key() -> Result<(), Box<dyn Error>> {
    test_parse(include_str!("test/key.air"), "test/key.air", key::expected)
}

#[test]
fn test_generate_key() -> Result<(), Box<dyn Error>> {
    test_generate(include_str!("test/key.air"), "test/key.air")
}

#[test]
fn test_parse_text() -> Result<(), Box<dyn Error>> {
    test_parse(include_str!("test/text.air"), "test/text.air", text::expected)
}

#[test]
fn test_generate_text() -> Result<(), Box<dyn Error>> {
    test_generate(include_str!("test/text.air"), "test/text.air")
}

#[test]
fn test_parse_int() -> Result<(), Box<dyn Error>> {
    test_parse(include_str!("test/int.air"), "test/int.air", int::expected)
}

#[test]
fn test_generate_int() -> Result<(), Box<dyn Error>> {
    test_generate(include_str!("test/int.air"), "test/int.air")
}

#[test]
fn test_parse_decimal() -> Result<(), Box<dyn Error>> {
    test_parse(include_str!("test/decimal.air"), "test/decimal.air", decimal::expected)
}

#[test]
fn test_generate_decimal() -> Result<(), Box<dyn Error>> {
    test_generate(include_str!("test/decimal.air"), "test/decimal.air")
}

#[test]
fn test_parse_byte() -> Result<(), Box<dyn Error>> {
    test_parse(include_str!("test/byte.air"), "test/byte.air", byte::expected)
}

#[test]
fn test_generate_byte() -> Result<(), Box<dyn Error>> {
    test_generate(include_str!("test/byte.air"), "test/byte.air")
}

#[test]
fn test_parse_cell() -> Result<(), Box<dyn Error>> {
    test_parse(include_str!("test/cell.air"), "test/cell.air", cell::expected)
}

#[test]
fn test_generate_cell() -> Result<(), Box<dyn Error>> {
    test_generate(include_str!("test/cell.air"), "test/cell.air")
}

#[test]
fn test_parse_pair() -> Result<(), Box<dyn Error>> {
    test_parse(include_str!("test/pair.air"), "test/pair.air", pair::expected)
}

#[test]
fn test_generate_pair() -> Result<(), Box<dyn Error>> {
    test_generate(include_str!("test/pair.air"), "test/pair.air")
}

#[test]
fn test_parse_call() -> Result<(), Box<dyn Error>> {
    test_parse(include_str!("test/call.air"), "test/call.air", call::expected)
}

#[test]
fn test_generate_call() -> Result<(), Box<dyn Error>> {
    test_generate(include_str!("test/call.air"), "test/call.air")
}

#[test]
fn test_parse_list() -> Result<(), Box<dyn Error>> {
    test_parse(include_str!("test/list.air"), "test/list.air", list::expected)
}

#[test]
fn test_generate_list() -> Result<(), Box<dyn Error>> {
    test_generate(include_str!("test/list.air"), "test/list.air")
}

#[test]
fn test_parse_map() -> Result<(), Box<dyn Error>> {
    test_parse(include_str!("test/map.air"), "test/map.air", map::expected)
}

#[test]
fn test_generate_map() -> Result<(), Box<dyn Error>> {
    test_generate(include_str!("test/map.air"), "test/map.air")
}

#[test]
fn test_doc() -> Result<(), Box<dyn Error>> {
    test_parse(include_str!("test/doc.air"), "test/doc.air", doc::expected)
}

#[test]
fn test_parse_illegal_example() -> Result<(), Box<dyn Error>> {
    test_parse_illegal(include_str!("test/illegal.air"), "test/illegal.air")
}

#[test]
fn test_parse_bad_example() -> Result<(), Box<dyn Error>> {
    test_parse_bad(include_str!("test/bad.air"), "test/bad.air")
}

mod unit;

mod bit;

mod key;

mod text;

mod int;

mod decimal;

mod byte;

mod cell;

mod pair;

mod call;

mod list;

mod map;

mod space;

mod scope;

mod doc;