rgb-strict-types 1.0.2

Strict types: confined generalized algebraic data types (GADT)
// Strict encoding schema library, implementing validation and parsing of strict encoded data
// against a schema.
//
// SPDX-License-Identifier: Apache-2.0
//
// Designed in 2019-2025 by Dr Maxim Orlovsky <orlovsky@ubideco.org>
// Written in 2024-2025 by Dr Maxim Orlovsky <orlovsky@ubideco.org>
//
// Copyright (C) 2022-2025 Laboratories for Ubiquitous Deterministic Computing (UBIDECO),
//                         Institute for Distributed and Cognitive Systems (InDCS), Switzerland.
// Copyright (C) 2022-2025 Dr Maxim Orlovsky.
// All rights under the above copyrights are reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
// in compliance with the License. You may obtain a copy of the License at
//
//        http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the License
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
// or implied. See the License for the specific language governing permissions and limitations under
// the License.

use std::cmp::Ordering;
use std::collections::HashSet;
use std::fmt::{self, Display, Formatter};
use std::hash::{Hash, Hasher};

use amplify::confinement::{NonEmptyOrdMap, TinyOrdSet};
use baid64::DisplayBaid64;
use encoding::StrictDumb;
use strict_encoding::{LibName, TypeName, STRICT_TYPES_LIB};

use crate::typelib::compile::CompileError;
use crate::typelib::id::TypeLibId;
use crate::typelib::ExternTypes;
use crate::{SemId, Ty, TypeRef};

pub trait LibSubref: TypeRef {}
impl LibSubref for LibRef {}
impl LibSubref for InlineRef {}
impl LibSubref for InlineRef1 {}
impl LibSubref for InlineRef2 {}

#[derive(Clone, Eq, PartialEq, Debug, Display)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = STRICT_TYPES_LIB)]
#[display("{lib_id}.{sem_id}")]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
pub struct ExternRef {
    pub lib_id: TypeLibId,
    pub sem_id: SemId,
}

impl ExternRef {
    pub fn with(lib_id: TypeLibId, sem_id: SemId) -> ExternRef { ExternRef { lib_id, sem_id } }
}

impl StrictDumb for Box<Ty<InlineRef1>> {
    fn strict_dumb() -> Self { Box::new(Ty::UNIT) }
}

#[derive(Clone, Eq, PartialEq, Debug, From)]
#[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = STRICT_TYPES_LIB, tags = order, dumb = { InlineRef::Inline(Box::new(Ty::strict_dumb())) })]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
pub enum InlineRef {
    #[from]
    Inline(Box<Ty<InlineRef1>>),
    Named(SemId),
    Extern(ExternRef),
}

impl TypeRef for InlineRef {
    fn is_compound(&self) -> bool {
        match self {
            InlineRef::Inline(ty) => ty.is_compound(),
            _ => false,
        }
    }
    fn is_byte(&self) -> bool {
        match self {
            InlineRef::Inline(ty) => ty.is_byte(),
            _ => false,
        }
    }
    fn is_unicode_char(&self) -> bool {
        match self {
            InlineRef::Inline(ty) => ty.is_unicode_char(),
            _ => false,
        }
    }
}

impl Display for InlineRef {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            InlineRef::Named(sem_id) => write!(f, "{sem_id}"),
            InlineRef::Extern(ext) => Display::fmt(ext, f),
            InlineRef::Inline(ty) => Display::fmt(ty, f),
        }
    }
}

#[derive(Clone, Eq, PartialEq, Debug, From)]
#[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = STRICT_TYPES_LIB, tags = order, dumb = { InlineRef1::Inline(Ty::strict_dumb()) })]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
pub enum InlineRef1 {
    #[from]
    Inline(Ty<InlineRef2>),
    Named(SemId),
    Extern(ExternRef),
}

impl TypeRef for InlineRef1 {
    fn is_compound(&self) -> bool {
        match self {
            InlineRef1::Inline(ty) => ty.is_compound(),
            _ => false,
        }
    }
    fn is_byte(&self) -> bool {
        match self {
            InlineRef1::Inline(ty) => ty.is_byte(),
            _ => false,
        }
    }
    fn is_unicode_char(&self) -> bool {
        match self {
            InlineRef1::Inline(ty) => ty.is_unicode_char(),
            _ => false,
        }
    }
}

impl Display for InlineRef1 {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            InlineRef1::Named(sem_id) => write!(f, "{sem_id}"),
            InlineRef1::Extern(ext) => Display::fmt(ext, f),
            InlineRef1::Inline(ty) => Display::fmt(ty, f),
        }
    }
}

#[derive(Clone, Eq, PartialEq, Debug, From)]
#[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = STRICT_TYPES_LIB, tags = order, dumb = { InlineRef2::Named(SemId::strict_dumb()) })]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
pub enum InlineRef2 {
    Named(SemId),
    Extern(ExternRef),
}

impl TypeRef for InlineRef2 {
    fn is_compound(&self) -> bool { false }
    fn is_byte(&self) -> bool { false }
    fn is_unicode_char(&self) -> bool { false }
}

impl Display for InlineRef2 {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            InlineRef2::Named(sem_id) => write!(f, "{sem_id}"),
            InlineRef2::Extern(ext) => Display::fmt(ext, f),
        }
    }
}

impl StrictDumb for Box<Ty<InlineRef>> {
    fn strict_dumb() -> Self { Box::new(Ty::UNIT) }
}

#[derive(Clone, Eq, PartialEq, Debug)]
#[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = STRICT_TYPES_LIB, tags = order, dumb = { LibRef::Inline(Box::new(Ty::strict_dumb())) })]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
pub enum LibRef {
    Inline(Box<Ty<InlineRef>>),
    Named(SemId),
    Extern(ExternRef),
}

impl TypeRef for LibRef {
    fn is_compound(&self) -> bool {
        match self {
            LibRef::Inline(ty) => ty.is_compound(),
            _ => false,
        }
    }
    fn is_byte(&self) -> bool {
        match self {
            LibRef::Inline(ty) => ty.is_byte(),
            _ => false,
        }
    }
    fn is_unicode_char(&self) -> bool {
        match self {
            LibRef::Inline(ty) => ty.is_unicode_char(),
            _ => false,
        }
    }
}

impl Display for LibRef {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            LibRef::Named(sem_id) => Display::fmt(sem_id, f),
            LibRef::Inline(ty) => Display::fmt(ty, f),
            LibRef::Extern(ext) => Display::fmt(ext, f),
        }
    }
}

#[derive(Clone, Eq, Debug)]
#[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = STRICT_TYPES_LIB)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Dependency {
    pub id: TypeLibId,
    pub name: LibName,
}

impl Hash for Dependency {
    fn hash<H: Hasher>(&self, state: &mut H) { self.id.hash(state) }
}

impl PartialEq for Dependency {
    fn eq(&self, other: &Self) -> bool { self.id == other.id || self.name == other.name }
}

impl PartialOrd for Dependency {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}

impl Ord for Dependency {
    fn cmp(&self, other: &Self) -> Ordering { self.id.cmp(&other.id) }
}

impl Dependency {
    pub fn with(id: TypeLibId, name: LibName) -> Self { Dependency { id, name } }
}

impl From<&TypeLib> for Dependency {
    fn from(lib: &TypeLib) -> Self {
        Dependency {
            id: lib.id(),
            name: lib.name.clone(),
        }
    }
}

impl Display for Dependency {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{}#{}", self.name, self.id.to_baid64_mnemonic())
    }
}

pub type TypeMap = NonEmptyOrdMap<TypeName, Ty<LibRef>, { u16::MAX as usize }>;

#[derive(Clone, Eq, PartialEq, Debug)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = STRICT_TYPES_LIB)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct TypeLib {
    pub name: LibName,
    pub dependencies: TinyOrdSet<Dependency>,
    pub extern_types: ExternTypes,
    pub types: TypeMap,
}

impl StrictDumb for TypeLib {
    fn strict_dumb() -> Self {
        TypeLib {
            name: LibName::strict_dumb(),
            dependencies: default!(),
            extern_types: default!(),
            types: TypeMap::with_key_value(tn!("DumbType"), Ty::strict_dumb()),
        }
    }
}

impl TypeLib {
    pub fn to_dependency(&self) -> Dependency { Dependency::with(self.id(), self.name.clone()) }

    pub fn to_dependency_types(&self) -> (Dependency, HashSet<SemId>) {
        let dependency = Dependency::with(self.id(), self.name.clone());
        let types = self.types.iter().map(|(name, ty)| ty.sem_id_named(name)).collect();
        (dependency, types)
    }

    pub fn import(&mut self, dependency: Dependency) -> Result<(), CompileError> {
        if self.dependencies.contains(&dependency) {
            return Err(CompileError::DuplicatedDependency(dependency));
        }
        self.dependencies.push(dependency).map_err(|_| CompileError::TooManyDependencies)?;
        Ok(())
    }

    pub fn populate(&mut self, name: TypeName, ty: Ty<LibRef>) -> Result<(), CompileError> {
        if self.types.contains_key(&name) {
            return Err(CompileError::DuplicateName(name));
        }
        self.types.insert(name, ty).map_err(|_| CompileError::TooManyTypes)?;
        Ok(())
    }

    // TODO: Check that all dependencies are used
}