zako 0.1.0

Small IR utilities inspired by RVSDG.
Documentation
//! Small IR utilities inspired by RVSDG

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RequestKind {
    Algebraic { family: String, operation: String },
    Host { service: String },
    Query { name: String },
    SourceHole { site: String, contract: String },
}

impl RequestKind {
    pub fn algebraic(family: impl Into<String>, operation: impl Into<String>) -> Self {
        Self::Algebraic {
            family: family.into(),
            operation: operation.into(),
        }
    }

    pub fn source_hole(site: impl Into<String>, contract: impl Into<String>) -> Self {
        Self::SourceHole {
            site: site.into(),
            contract: contract.into(),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RequestInterface {
    answer: String,
    requests: Vec<RequestKind>,
}

impl RequestInterface {
    pub fn new(answer: impl Into<String>) -> Self {
        Self {
            answer: answer.into(),
            requests: Vec::new(),
        }
    }

    pub fn with(mut self, request: RequestKind) -> Self {
        self.requests.push(request);
        self
    }

    pub fn answer(&self) -> &str {
        &self.answer
    }

    pub fn requests(&self) -> &[RequestKind] {
        &self.requests
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RequestSite {
    kind: RequestKind,
    result: String,
    local_answer: String,
}

impl RequestSite {
    pub fn algebraic(
        family: impl Into<String>,
        operation: impl Into<String>,
        result: impl Into<String>,
        local_answer: impl Into<String>,
    ) -> Self {
        Self {
            kind: RequestKind::algebraic(family, operation),
            result: result.into(),
            local_answer: local_answer.into(),
        }
    }

    pub fn residual_interface(&self) -> RequestInterface {
        RequestInterface::new(self.local_answer.clone()).with(self.kind.clone())
    }

    pub fn result(&self) -> &str {
        &self.result
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Delimiter {
    input_answer: String,
    output_answer: String,
    handled: Vec<RequestKind>,
}

impl Delimiter {
    pub fn new(input_answer: impl Into<String>, output_answer: impl Into<String>) -> Self {
        Self {
            input_answer: input_answer.into(),
            output_answer: output_answer.into(),
            handled: Vec::new(),
        }
    }

    pub fn handles(mut self, family: impl Into<String>, operation: impl Into<String>) -> Self {
        self.handled.push(RequestKind::algebraic(family, operation));
        self
    }

    pub fn transform(&self, interface: &RequestInterface) -> RequestInterface {
        assert_eq!(interface.answer(), self.input_answer);

        interface
            .requests()
            .iter()
            .filter(|request| !self.handled.contains(request))
            .cloned()
            .fold(
                RequestInterface::new(self.output_answer.clone()),
                RequestInterface::with,
            )
    }
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct CommuteEvidence {
    request_order: bool,
    resource: bool,
    boundary: bool,
}

impl CommuteEvidence {
    pub fn all() -> Self {
        Self {
            request_order: true,
            resource: true,
            boundary: true,
        }
    }

    pub fn resource_only() -> Self {
        Self {
            resource: true,
            ..Self::default()
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CommuteError {
    MissingOrderProof,
    MissingResourceProof,
    MissingBoundaryProof,
}

pub fn can_commute(
    _left: &RequestSite,
    _right: &RequestSite,
    evidence: CommuteEvidence,
) -> Result<(), CommuteError> {
    if !evidence.request_order {
        return Err(CommuteError::MissingOrderProof);
    }
    if !evidence.resource {
        return Err(CommuteError::MissingResourceProof);
    }
    if !evidence.boundary {
        return Err(CommuteError::MissingBoundaryProof);
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn site_exports_interface() {
        let site = RequestSite::algebraic("Console", "read_line", "String", "Answer");

        let interface = site.residual_interface();

        assert_eq!(interface.answer(), "Answer");
        assert_eq!(
            interface.requests(),
            &[RequestKind::algebraic("Console", "read_line")]
        );
    }

    #[test]
    fn transform_keeps_unhandled_request() {
        let input = RequestInterface::new("A")
            .with(RequestKind::algebraic("State", "get"))
            .with(RequestKind::algebraic("State", "put"));
        let delimiter = Delimiter::new("A", "B").handles("State", "get");

        let output = delimiter.transform(&input);

        assert_eq!(output.answer(), "B");
        assert_eq!(output.requests(), &[RequestKind::algebraic("State", "put")]);
    }

    #[test]
    fn commute_requires_all_evidence() {
        let a = RequestSite::algebraic("Log", "emit", "Unit", "Answer");
        let b = RequestSite::algebraic("Log", "emit", "Unit", "Answer");

        assert_eq!(
            can_commute(&a, &b, CommuteEvidence::resource_only()),
            Err(CommuteError::MissingOrderProof)
        );
        assert!(can_commute(&a, &b, CommuteEvidence::all()).is_ok());
    }
}