airlang 0.25.0

Air is a minimalist and universal programming language.
Documentation
use std::mem::swap;

use const_format::concatcp;
use num_traits::ToPrimitive;

use crate::bug;
use crate::cfg::CfgMod;
use crate::cfg::extend_func;
use crate::semantics::cfg::Cfg;
use crate::semantics::core::PREFIX_CELL;
use crate::semantics::func::CtxConstInputAwareFunc;
use crate::semantics::func::CtxConstInputFreeFunc;
use crate::semantics::func::CtxMutInputAwareFunc;
use crate::semantics::func::CtxMutInputFreeFunc;
use crate::semantics::val::LIST;
use crate::semantics::val::PrimFuncVal;
use crate::semantics::val::Val;
use crate::type_::Int;
use crate::type_::List;
use crate::type_::Pair;

// todo design
#[derive(Clone)]
pub struct ListLib {
    pub get_length: PrimFuncVal,
    pub set: PrimFuncVal,
    pub set_many: PrimFuncVal,
    pub get: PrimFuncVal,
    pub get_many: PrimFuncVal,
    pub insert: PrimFuncVal,
    pub insert_many: PrimFuncVal,
    pub remove: PrimFuncVal,
    pub remove_many: PrimFuncVal,
    pub push: PrimFuncVal,
    pub push_many: PrimFuncVal,
    pub pop: PrimFuncVal,
    pub pop_many: PrimFuncVal,
    pub clear: PrimFuncVal,
}

pub const GET_LENGTH: &str = concatcp!(PREFIX_CELL, LIST, ".get_length");
pub const SET: &str = concatcp!(PREFIX_CELL, LIST, ".set");
pub const SET_MANY: &str = concatcp!(PREFIX_CELL, LIST, ".set_many");
pub const GET: &str = concatcp!(PREFIX_CELL, LIST, ".get");
pub const GET_MANY: &str = concatcp!(PREFIX_CELL, LIST, ".get_many");
pub const INSERT: &str = concatcp!(PREFIX_CELL, LIST, ".insert");
pub const INSERT_MANY: &str = concatcp!(PREFIX_CELL, LIST, ".insert_many");
pub const REMOVE: &str = concatcp!(PREFIX_CELL, LIST, ".remove");
pub const REMOVE_MANY: &str = concatcp!(PREFIX_CELL, LIST, ".remove_many");
pub const PUSH: &str = concatcp!(PREFIX_CELL, LIST, ".push");
pub const PUSH_MANY: &str = concatcp!(PREFIX_CELL, LIST, ".push_many");
pub const POP: &str = concatcp!(PREFIX_CELL, LIST, ".pop");
pub const POP_MANY: &str = concatcp!(PREFIX_CELL, LIST, ".pop_many");
pub const CLEAR: &str = concatcp!(PREFIX_CELL, LIST, ".clear");

impl Default for ListLib {
    fn default() -> Self {
        ListLib {
            get_length: CtxConstInputFreeFunc { fn_: get_length }.build(),
            set: CtxMutInputAwareFunc { fn_: set }.build(),
            set_many: CtxMutInputAwareFunc { fn_: set_many }.build(),
            get: CtxConstInputAwareFunc { fn_: get }.build(),
            get_many: CtxConstInputAwareFunc { fn_: get_many }.build(),
            insert: CtxMutInputAwareFunc { fn_: insert }.build(),
            insert_many: CtxMutInputAwareFunc { fn_: insert_many }.build(),
            remove: CtxMutInputAwareFunc { fn_: remove }.build(),
            remove_many: CtxMutInputAwareFunc { fn_: remove_many }.build(),
            push: CtxMutInputAwareFunc { fn_: push }.build(),
            push_many: CtxMutInputAwareFunc { fn_: push_many }.build(),
            pop: CtxMutInputFreeFunc { fn_: pop }.build(),
            pop_many: CtxMutInputAwareFunc { fn_: pop_many }.build(),
            clear: CtxMutInputFreeFunc { fn_: clear }.build(),
        }
    }
}

impl CfgMod for ListLib {
    fn extend(self, cfg: &mut Cfg) {
        extend_func(cfg, GET_LENGTH, self.get_length);
        extend_func(cfg, SET, self.set);
        extend_func(cfg, SET_MANY, self.set_many);
        extend_func(cfg, GET, self.get);
        extend_func(cfg, GET_MANY, self.get_many);
        extend_func(cfg, INSERT, self.insert);
        extend_func(cfg, INSERT_MANY, self.insert_many);
        extend_func(cfg, REMOVE, self.remove);
        extend_func(cfg, REMOVE_MANY, self.remove_many);
        extend_func(cfg, PUSH, self.push);
        extend_func(cfg, PUSH_MANY, self.push_many);
        extend_func(cfg, POP, self.pop);
        extend_func(cfg, POP_MANY, self.pop_many);
        extend_func(cfg, CLEAR, self.clear);
    }
}

pub fn get_length(cfg: &mut Cfg, ctx: &Val) -> Val {
    let Val::List(list) = ctx else {
        return bug!(cfg, "{GET_LENGTH}: expected context to be a list, but got {ctx}");
    };
    let len: Int = list.len().into();
    Val::Int(len.into())
}

pub fn set(cfg: &mut Cfg, ctx: &mut Val, input: Val) -> Val {
    let Val::List(list) = ctx else {
        return bug!(cfg, "{SET}: expected context to be a list, but got {ctx}");
    };
    let Val::Pair(index_value) = input else {
        return bug!(cfg, "{SET}: expected input to be a pair, but got {input}");
    };
    let index_value = Pair::from(index_value);
    let index = index_value.left;
    let Some(i) = to_index(cfg, SET, index) else {
        return Val::default();
    };
    let mut value = index_value.right;
    let Some(current) = list.get_mut(i) else {
        return bug!(cfg, "{SET}: index {i} should < list.len {}", list.len());
    };
    swap(current, &mut value);
    value
}

pub fn set_many(cfg: &mut Cfg, ctx: &mut Val, input: Val) -> Val {
    let Val::List(list) = ctx else {
        return bug!(cfg, "{SET_MANY}: expected context to be a list, but got {ctx}");
    };
    let Val::Pair(index_value) = input else {
        return bug!(cfg, "{SET_MANY}: expected input to be a pair, but got {input}");
    };
    let index_value = Pair::from(index_value);
    let index = index_value.left;
    let Some(i) = to_index(cfg, SET_MANY, index) else {
        return Val::default();
    };
    let Val::List(values) = index_value.right else {
        return bug!(cfg, "{SET_MANY}: expected input.right to be a list, \
            but got {}", index_value.right);
    };
    let values = List::from(values);
    let end = i + values.len();
    if end > list.len() {
        return bug!(cfg, "{SET_MANY}: end {end} should <= list.len {}", list.len());
    }
    let ret: List<Val> = list.splice(i .. end, values).collect();
    Val::List(ret.into())
}

pub fn get(cfg: &mut Cfg, ctx: &Val, input: Val) -> Val {
    let Val::List(list) = ctx else {
        return bug!(cfg, "{GET}: expected context to be a list, but got {ctx}");
    };
    let Some(i) = to_index(cfg, GET, input) else {
        return Val::default();
    };
    let Some(val) = list.get(i) else {
        return bug!(cfg, "{GET}: index {i} should < list.len {}", list.len());
    };
    val.clone()
}

pub fn get_many(cfg: &mut Cfg, ctx: &Val, input: Val) -> Val {
    let Val::List(list) = ctx else {
        return bug!(cfg, "{GET_MANY}: expected context to be a list, but got {ctx}");
    };
    let Val::Pair(range) = input else {
        return bug!(cfg, "{GET_MANY}: expected input to be a pair, but got {input}");
    };
    let range = Pair::from(range);
    let Some((from, to)) = to_range(cfg, GET_MANY, range) else {
        return Val::default();
    };
    let from = from.unwrap_or_default();
    let to = to.unwrap_or(list.len());
    let Some(slice) = list.get(from .. to) else {
        return bug!(cfg, "{GET_MANY}: range {from} : {to} should be in 0 : {}", list.len());
    };
    Val::List(List::from(slice.to_owned()).into())
}

pub fn insert(cfg: &mut Cfg, ctx: &mut Val, input: Val) -> Val {
    let Val::List(list) = ctx else {
        return bug!(cfg, "{INSERT}: expected context to be a list, but got {ctx}");
    };
    let Val::Pair(index_value) = input else {
        return bug!(cfg, "{INSERT}: expected input to be a pair, but got {input}");
    };
    let index_value = Pair::from(index_value);
    let index = index_value.left;
    let Some(i) = to_index(cfg, INSERT, index) else {
        return Val::default();
    };
    let value = index_value.right;
    if i > list.len() {
        return bug!(cfg, "{INSERT}: index {i} should <= list.len {}", list.len());
    }
    list.insert(i, value);
    Val::default()
}

pub fn insert_many(cfg: &mut Cfg, ctx: &mut Val, input: Val) -> Val {
    let Val::List(list) = ctx else {
        return bug!(cfg, "{INSERT_MANY}: expected context to be a list, but got {ctx}");
    };
    let Val::Pair(index_value) = input else {
        return bug!(cfg, "{INSERT_MANY}: expected input to be a pair, but got {input}");
    };
    let index_value = Pair::from(index_value);
    let index = index_value.left;
    let Some(i) = to_index(cfg, INSERT_MANY, index) else {
        return Val::default();
    };
    let Val::List(values) = index_value.right else {
        return bug!(cfg, "{INSERT_MANY}: expected input.right to be a list, \
            but got {}", index_value.right);
    };
    let values = List::from(values);
    if i > list.len() {
        return bug!(cfg, "{INSERT_MANY}: index {i} should <= list.len {}", list.len());
    }
    list.splice(i .. i, values);
    Val::default()
}

pub fn remove(cfg: &mut Cfg, ctx: &mut Val, input: Val) -> Val {
    let Val::List(list) = ctx else {
        return bug!(cfg, "{REMOVE}: expected context to be a list, but got {ctx}");
    };
    let Some(i) = to_index(cfg, REMOVE, input) else {
        return Val::default();
    };
    if i >= list.len() {
        return bug!(cfg, "{REMOVE}: index {i} should < list.len {}", list.len());
    }
    list.remove(i)
}

pub fn remove_many(cfg: &mut Cfg, ctx: &mut Val, input: Val) -> Val {
    let Val::List(list) = ctx else {
        return bug!(cfg, "{REMOVE_MANY}: expected context to be a list, but got {ctx}");
    };
    let Val::Pair(range) = input else {
        return bug!(cfg, "{REMOVE_MANY}: expected input to be a pair, but got {input}");
    };
    let range = Pair::from(range);
    let Some((from, to)) = to_range(cfg, REMOVE_MANY, range) else {
        return Val::default();
    };
    let from = from.unwrap_or_default();
    let to = to.unwrap_or(list.len());
    if from > to || to > list.len() {
        return bug!(cfg, "{REMOVE_MANY}: range {from} : {to} should be in 0 : {}", list.len());
    }
    let ret: List<Val> = list.splice(from .. to, Vec::new()).collect();
    Val::List(ret.into())
}

pub fn push(cfg: &mut Cfg, ctx: &mut Val, input: Val) -> Val {
    let Val::List(list) = ctx else {
        return bug!(cfg, "{PUSH}: expected context to be a list, but got {ctx}");
    };
    list.push(input);
    Val::default()
}

pub fn push_many(cfg: &mut Cfg, ctx: &mut Val, input: Val) -> Val {
    let Val::List(list) = ctx else {
        return bug!(cfg, "{PUSH_MANY}: expected context to be a list, but got {ctx}");
    };
    let Val::List(mut values) = input else {
        return bug!(cfg, "{PUSH_MANY}: expected input to be a list, but got {input}");
    };
    list.append(&mut values);
    Val::default()
}

pub fn pop(cfg: &mut Cfg, ctx: &mut Val) -> Val {
    let Val::List(list) = ctx else {
        return bug!(cfg, "{POP}: expected context to be a list, but got {ctx}");
    };
    let Some(val) = list.pop() else {
        return bug!(cfg, "{POP}: expected list to be non-empty");
    };
    val
}

pub fn pop_many(cfg: &mut Cfg, ctx: &mut Val, input: Val) -> Val {
    let Val::List(list) = ctx else {
        return bug!(cfg, "{POP_MANY}: expected context to be a list, but got {ctx}");
    };
    let Val::Int(i) = input else {
        return bug!(cfg, "{POP_MANY}: expected input to be an integer, but got {input}");
    };
    let Some(i) = i.to_usize() else {
        return bug!(cfg, "{POP_MANY}: index {i} should <= list.len {}", list.len());
    };
    let list = &mut **list;
    if i > list.len() {
        return bug!(cfg, "{POP_MANY}: index {i} should <= list.len {}", list.len());
    }
    let start = list.len() - i;
    let list = list.split_off(start);
    let list: List<Val> = list.into();
    Val::List(list.into())
}

pub fn clear(cfg: &mut Cfg, ctx: &mut Val) -> Val {
    let Val::List(list) = ctx else {
        return bug!(cfg, "{CLEAR}: expected context to be a list, but got {ctx}");
    };
    list.clear();
    Val::default()
}

fn to_index(cfg: &mut Cfg, key: &str, val: Val) -> Option<usize> {
    let Val::Int(i) = val else {
        bug!(cfg, "{key}: expected index to be an integer, but got {val}");
        return None;
    };
    i.to_usize()
}

fn to_range(
    cfg: &mut Cfg, key: &str, pair: Pair<Val, Val>,
) -> Option<(Option<usize>, Option<usize>)> {
    let from = match pair.left {
        Val::Int(i) => Some(i.to_usize()?),
        Val::Unit(_) => None,
        v => {
            bug!(cfg, "{key}: expected range.from to be an integer or a unit, but got {v}");
            return None;
        },
    };
    let to = match pair.right {
        Val::Int(i) => Some(i.to_usize()?),
        Val::Unit(_) => None,
        v => {
            bug!(cfg, "{key}: expected range.to to be an integer or a unit, but got {v}");
            return None;
        },
    };
    Some((from, to))
}