typr-core 0.5.5

Core type checking and transpilation logic for TypR - a typed superset of R
Documentation
use crate::components::error_message::help_data::HelpData;
use crate::components::language::Lang;
use crate::components::r#type::tint::Tint;
use crate::components::r#type::type_system::TypeSystem;
use crate::components::r#type::vector_type::VecType;
use crate::components::r#type::Type;
use crate::processes::parsing::operation_priority::TokenKind;
use crate::utils::builder;
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::bytes::complete::take_until;
use nom::character::complete::char;
use nom::character::complete::multispace0;
use nom::combinator::recognize;
use nom::sequence::terminated;
use nom::IResult;
use nom::Parser;
use nom_locate::LocatedSpan;
use serde::{Deserialize, Serialize};

type Span<'a> = LocatedSpan<&'a str, String>;

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Op {
    And(HelpData),
    And2(HelpData),
    Eq(HelpData),
    Eq2(HelpData),
    NotEq(HelpData),
    Add(HelpData),
    Add2(HelpData),
    Pipe(HelpData),
    Dot(HelpData),
    Pipe2(HelpData),
    Dot2(HelpData),
    Or(HelpData),
    Or2(HelpData),
    Minus(HelpData),
    Minus2(HelpData),
    Mul(HelpData),
    Mul2(HelpData),
    In(HelpData),
    At(HelpData),
    At2(HelpData),
    Div(HelpData),
    Div2(HelpData),
    LesserThan(HelpData),
    GreaterThan(HelpData),
    LesserOrEqual(HelpData),
    GreaterOrEqual(HelpData),
    Modulo(HelpData),
    Modulo2(HelpData),
    Dollar(HelpData),
    Dollar2(HelpData),
    Custom(String, HelpData),
    Empty(HelpData),
    AsExcl(HelpData),
}

/// Reinterprets the value-level AST produced for a bracket type expression
/// (`[Any, int]`, `Vec[N, T]`, `Array[N, T]`) appearing as the right-hand side
/// of `as!` as the `Type` it denotes. These shapes parse as plain `Lang`
/// values (`Array`/`ArrayIndexing`/`Variable`) since `as!`'s right operand
/// goes through the ordinary expression grammar, not the type grammar — see
/// `Op::combine`. The `Vec`/`Array` keyword prefix is not preserved in the
/// resulting `VecType` since `Type`'s `PartialEq` ignores it anyway (it only
/// matters for named aliases, which inline literals are not).
fn lang_to_cast_type(lang: &Lang) -> Option<Type> {
    match lang {
        Lang::Variable {
            name, help_data, ..
        } => Some(match name.as_str() {
            "int" => builder::integer_type_default(),
            "num" => builder::number_type(),
            "bool" => builder::boolean_type(),
            "char" => builder::character_type_default(),
            "Any" => builder::any_type(),
            _ => Type::Alias(name.clone(), vec![], false, help_data.clone()),
        }),
        Lang::Integer { value, help_data } => {
            Some(Type::Integer(Tint::Val(*value), help_data.clone()))
        }
        Lang::Array { value, help_data } => {
            let (idx, elem) = match value.as_slice() {
                [elem] => (builder::any_type(), elem),
                [idx, elem] => (lang_to_cast_type(idx)?, elem),
                _ => return None,
            };
            Some(Type::Vec(
                VecType::S3,
                Box::new(idx),
                Box::new(lang_to_cast_type(elem)?),
                help_data.clone(),
            ))
        }
        Lang::ArrayIndexing {
            identifier,
            indexing,
            help_data,
        } => {
            match identifier.as_ref() {
                Lang::Variable { name, .. } if name == "Vec" || name == "Array" => {}
                _ => return None,
            }
            let value = match indexing.as_ref() {
                Lang::Array { value, .. } => value,
                _ => return None,
            };
            let (idx, elem) = match value.as_slice() {
                [elem] => (builder::any_type(), elem),
                [idx, elem] => (lang_to_cast_type(idx)?, elem),
                _ => return None,
            };
            Some(Type::Vec(
                VecType::S3,
                Box::new(idx),
                Box::new(lang_to_cast_type(elem)?),
                help_data.clone(),
            ))
        }
        _ => None,
    }
}

impl Op {
    pub fn to_type(&self) -> Option<Type> {
        match self {
            Op::In(h) => Some(Type::In(h.clone())),
            _ => None,
        }
    }

    pub fn get_token_type(&self) -> TokenKind {
        TokenKind::Operator
    }

    pub fn get_binding_power(&self) -> i32 {
        match self {
            Op::AsExcl(_) => 4,
            Op::Dot(_)
            | Op::Dot2(_)
            | Op::Pipe(_)
            | Op::Pipe2(_)
            | Op::Dollar(_)
            | Op::Dollar2(_)
            | Op::In(_) => 4,
            Op::Mul(_)
            | Op::Mul2(_)
            | Op::Div(_)
            | Op::Div2(_)
            | Op::Modulo(_)
            | Op::Modulo2(_)
            | Op::At(_)
            | Op::At2(_) => 3,
            Op::Add(_) | Op::Add2(_) | Op::Minus(_) | Op::Minus2(_) => 2,
            _ => 1,
        }
    }

    pub fn combine(self, left: Lang, right: Lang) -> Lang {
        if let Op::AsExcl(_) = self {
            let (type_name, literal_type) = match &right {
                Lang::Variable { name, .. } => (name.clone(), None),
                _ => match lang_to_cast_type(&right) {
                    Some(t) => (t.pretty(), Some(t)),
                    None => ("Unknown".to_string(), None),
                },
            };
            return Lang::ValidatingCast {
                expression: Box::new(left.clone()),
                type_name,
                literal_type,
                help_data: left.get_help_data(),
            };
        }
        Lang::Operator {
            operator: self,
            rhs: Box::new(left.clone()),
            lhs: Box::new(right),
            help_data: left.get_help_data(),
        }
    }

    pub fn get_help_data(&self) -> HelpData {
        match self {
            Op::AsExcl(h) => h.clone(),
            Op::Empty(h) => h.clone(),
            Op::Custom(_, h) => h.clone(),
            Op::Dollar(h) => h.clone(),
            Op::Dollar2(h) => h.clone(),
            Op::Modulo2(h) => h.clone(),
            Op::Modulo(h) => h.clone(),
            Op::LesserOrEqual(h) => h.clone(),
            Op::GreaterOrEqual(h) => h.clone(),
            Op::Add(h) => h.clone(),
            Op::Add2(h) => h.clone(),
            Op::And(h) => h.clone(),
            Op::And2(h) => h.clone(),
            Op::Or(h) => h.clone(),
            Op::Or2(h) => h.clone(),
            Op::Eq(h) => h.clone(),
            Op::Eq2(h) => h.clone(),
            Op::NotEq(h) => h.clone(),
            Op::Pipe(h) => h.clone(),
            Op::Pipe2(h) => h.clone(),
            Op::Dot(h) => h.clone(),
            Op::Dot2(h) => h.clone(),
            Op::Minus(h) => h.clone(),
            Op::Minus2(h) => h.clone(),
            Op::Mul(h) => h.clone(),
            Op::Mul2(h) => h.clone(),
            Op::In(h) => h.clone(),
            Op::At(h) => h.clone(),
            Op::At2(h) => h.clone(),
            Op::Div(h) => h.clone(),
            Op::Div2(h) => h.clone(),
            Op::LesserThan(h) => h.clone(),
            Op::GreaterThan(h) => h.clone(),
        }
    }
}

fn bool_op(s: Span) -> IResult<Span, Span> {
    // Deliberately no bare `tag("=")` alternative here: a single `=` is never
    // a binary operator in TypR (`Op::Eq2` has no handling anywhere in
    // type-checking/transpiling — it would panic via `compute_operators`'s
    // catch-all if ever produced). `=` is reserved for dedicated grammar
    // positions parsed directly elsewhere: named record fields (`x = 1`),
    // `assign()`'s `x = expr;`, and `fn(...)` default parameter values.
    terminated(
        alt((
            tag("<="),
            tag(">="),
            tag("=="),
            tag("!="),
            tag("<"),
            tag(">"),
            tag("and"),
            tag("&&"),
            tag("&"),
            tag("or"),
            tag("||"),
            tag("|"),
        )),
        multispace0,
    )
    .parse(s)
}

fn get_op(ls: LocatedSpan<&str, String>) -> Op {
    match ls.clone().into_fragment() {
        "+" => Op::Add(ls.into()),
        "++" => Op::Add2(ls.into()),
        "-" => Op::Minus(ls.into()),
        "--" => Op::Minus2(ls.into()),
        "*" => Op::Mul(ls.into()),
        "**" => Op::Mul2(ls.into()),
        "/" => Op::Div(ls.into()),
        "//" => Op::Div2(ls.into()),
        "@@" => Op::At2(ls.into()),
        "@" => Op::At(ls.into()),
        "%%" => Op::Modulo2(ls.into()),
        "%" => Op::Modulo(ls.into()),
        "|>" => Op::Pipe(ls.into()),
        "|>>" => Op::Pipe2(ls.into()),
        "::" => Op::Dollar(ls.into()),
        "=" => Op::Eq2(ls.into()),
        "." => Op::Dot(ls.into()),
        ".." => Op::Dot2(ls.into()),
        "$" => Op::Dollar(ls.into()),
        "$$" => Op::Dollar2(ls.into()),
        "==" => Op::Eq(ls.into()),
        "!=" => Op::NotEq(ls.into()),
        "<=" => Op::LesserOrEqual(ls.into()),
        ">=" => Op::GreaterOrEqual(ls.into()),
        "<" => Op::LesserThan(ls.into()),
        ">" => Op::GreaterThan(ls.into()),
        "in " => Op::In(ls.into()),
        "and" => Op::And2(ls.into()),
        "&&" => Op::And2(ls.into()),
        "&" => Op::And(ls.into()),
        "or" => Op::Or2(ls.into()),
        "||" => Op::Or2(ls.into()),
        "|" => Op::Or(ls.into()),
        n => Op::Custom(n.to_string(), ls.into()),
    }
}

pub fn custom_op(s: Span) -> IResult<Span, Span> {
    recognize((char('%'), take_until("%"), char('%'))).parse(s)
}

fn pipe_op(s: Span) -> IResult<Span, Span> {
    alt((
        tag("|>>"),
        tag("|>"),
        tag("::"),
        tag(".."),
        tag("."),
        tag("$$"),
        tag("$"),
    ))
    .parse(s)
}

pub fn op(s: Span) -> IResult<Span, Op> {
    let res = terminated(
        alt((
            custom_op,
            pipe_op,
            bool_op,
            tag("in "),
            tag("++"),
            tag("+"),
            tag("--"),
            tag("-"),
            tag("@@"),
            tag("@"),
            tag("**"),
            tag("*"),
            tag("//"),
            tag("/"),
            tag("%%"),
            tag("%"),
        )),
        multispace0,
    )
    .parse(s);
    match res {
        Ok((s, ls)) => Ok((s, get_op(ls))),
        Err(r) => Err(r),
    }
}

pub fn get_string(op: &Op) -> String {
    match op {
        Op::AsExcl(_) => "as!".to_string(),
        Op::In(_) => "in".to_string(),
        Op::And(_) => "&".to_string(),
        Op::And2(_) => "&&".to_string(),
        Op::Or(_) => "|".to_string(),
        Op::Or2(_) => "||".to_string(),
        Op::Add(_) => "+".to_string(),
        Op::Add2(_) => "++".to_string(),
        Op::Minus(_) => "-".to_string(),
        Op::Minus2(_) => "--".to_string(),
        Op::Mul(_) => "*".to_string(),
        Op::Mul2(_) => "**".to_string(),
        Op::Div(_) => "/".to_string(),
        Op::Div2(_) => "//".to_string(),
        Op::At(_) => "@".to_string(),
        Op::At2(_) => "@@".to_string(),
        Op::Pipe(_) => "|>".to_string(),
        Op::Pipe2(_) => "|>>".to_string(),
        Op::Dot(_) => ".".to_string(),
        Op::Dot2(_) => "..".to_string(),
        Op::LesserThan(_) => "<".to_string(),
        Op::GreaterThan(_) => ">".to_string(),
        Op::LesserOrEqual(_) => "<=".to_string(),
        Op::GreaterOrEqual(_) => ">=".to_string(),
        Op::Modulo2(_) => "%%".to_string(),
        Op::Modulo(_) => "%".to_string(),
        Op::Dollar2(_) => "$$".to_string(),
        Op::Dollar(_) => "$".to_string(),
        Op::Eq(_) => "==".to_string(),
        Op::Eq2(_) => "=".to_string(),
        Op::NotEq(_) => "!=".to_string(),
        n => todo!("operator to_string not implemented for {:?}", n),
    }
}

use std::fmt;
impl fmt::Display for Op {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let res = get_string(self);
        write!(f, "{}", res)
    }
}