blr-lang 0.1.0

A language implementation that provides type safe dataframes
Documentation
//! Type expressions for types external to blr.
//! These types mirror the WASM Component model type system.

use crate::compiler::sexpr::{FromSExpr, ParseError, SExpr, ToSExpr, expect_n};

#[derive(Debug, Clone, PartialEq)]
pub enum ExternalType {
    Unit,
    Int,
    Float,
    String,
    Resource(String),
    Fun(FunctionType),
    Record(String, Vec<(String, ExternalType)>),
}

#[derive(Debug, Clone, PartialEq)]
pub struct FunctionType {
    pub parameter_names: Vec<String>,
    pub parameter_typs: Vec<ExternalType>,
    pub ret: Box<ExternalType>,
}

// ── ToSExpr Implementations ────────────────────────────────────────────

impl ToSExpr<()> for FunctionType {
    fn to_sexpr(&self, _config: &()) -> SExpr {
        let mut elements = vec![SExpr::Atom("fn".into())];
        let mut param_elements = Vec::new();
        for (name, typ) in self.parameter_names.iter().zip(&self.parameter_typs) {
            param_elements.push(SExpr::List(vec![
                SExpr::Atom(name.clone()),
                typ.to_sexpr(&()),
            ]));
        }
        elements.push(SExpr::List(param_elements));
        elements.push(self.ret.to_sexpr(&()));
        SExpr::List(elements)
    }
}

impl ToSExpr<()> for ExternalType {
    fn to_sexpr(&self, _config: &()) -> SExpr {
        match self {
            ExternalType::Unit => SExpr::List(vec![SExpr::Atom("unit".into())]),
            ExternalType::Int => SExpr::List(vec![SExpr::Atom("int".into())]),
            ExternalType::Float => SExpr::List(vec![SExpr::Atom("float".into())]),
            ExternalType::String => SExpr::List(vec![SExpr::Atom("string".into())]),
            ExternalType::Resource(name) => SExpr::List(vec![
                SExpr::Atom("resource".into()),
                SExpr::Atom(name.clone()),
            ]),
            ExternalType::Fun(fn_type) => fn_type.to_sexpr(&()),
            ExternalType::Record(name, fields) => {
                let mut elements = vec![SExpr::Atom("record".into()), SExpr::Atom(name.clone())];
                for (label, typ) in fields {
                    elements.push(SExpr::List(vec![
                        SExpr::Atom(label.clone()),
                        typ.to_sexpr(&()),
                    ]));
                }
                SExpr::List(elements)
            }
        }
    }
}

// ── FromSExpr Implementations ──────────────────────────────────────────

impl FromSExpr<()> for FunctionType {
    fn from_sexp(s: &SExpr, _ctx: &mut ()) -> Result<Self, ParseError> {
        let args = s.args().ok_or(ParseError::ExpectedTag {
            expected: "fn",
            found: None,
        })?;
        if args.len() < 2 {
            return Err(ParseError::ExpectedArgs {
                expected: 2,
                found: args.len(),
            });
        }
        let params = match &args[0] {
            SExpr::List(items) => {
                let mut result = Vec::new();
                let mut names = Vec::new();
                for item in items.iter() {
                    if let Some(tagged) = item.args()
                        && !tagged.is_empty()
                    {
                        if let Some(name) = item.tag() {
                            names.push(name.to_string());
                        }
                        result.push(ExternalType::from_sexp(&tagged[0], _ctx)?);
                    }
                }
                (names, result)
            }
            _ => {
                let typ = ExternalType::from_sexp(&args[0], _ctx)?;
                (vec![String::new()], vec![typ])
            }
        };
        let ret = ExternalType::from_sexp(&args[1], _ctx)?;
        Ok(FunctionType {
            parameter_names: params.0,
            parameter_typs: params.1,
            ret: Box::new(ret),
        })
    }
}

impl FromSExpr<()> for ExternalType {
    fn from_sexp(s: &SExpr, _ctx: &mut ()) -> Result<Self, ParseError> {
        match s.tag() {
            Some("unit") => Ok(ExternalType::Unit),
            Some("int") => Ok(ExternalType::Int),
            Some("float") => Ok(ExternalType::Float),
            Some("string") => Ok(ExternalType::String),
            Some("resource") => {
                let args: &[SExpr; 1] = expect_n("resource", s.args())?;
                let name = match &args[0] {
                    SExpr::Atom(s) => s.clone(),
                    _ => {
                        return Err(ParseError::ExpectedTag {
                            expected: "resource name",
                            found: args[0].tag().map(|s| s.to_string()),
                        });
                    }
                };
                Ok(ExternalType::Resource(name))
            }
            Some("record") => {
                let args = s.args().ok_or(ParseError::ExpectedTag {
                    expected: "record",
                    found: None,
                })?;
                if args.len() < 2 {
                    return Err(ParseError::ExpectedArgs {
                        expected: 2,
                        found: args.len(),
                    });
                }
                let name = match &args[0] {
                    SExpr::Atom(s) => s.clone(),
                    _ => {
                        return Err(ParseError::ExpectedTag {
                            expected: "record name",
                            found: args[0].tag().map(|s| s.to_string()),
                        });
                    }
                };
                let mut fields = Vec::new();
                let mut i = 1;
                while i < args.len() {
                    let field = match &args[i] {
                        SExpr::List(items) => {
                            let items: &[SExpr; 2] = expect_n("field", Some(&items[..]))?;
                            let field_name = match &items[0] {
                                SExpr::Atom(s) => s.clone(),
                                _ => {
                                    return Err(ParseError::ExpectedTag {
                                        expected: "field name",
                                        found: items[0].tag().map(|s| s.to_string()),
                                    });
                                }
                            };
                            let field_type = ExternalType::from_sexp(&items[1], _ctx)?;
                            (field_name, field_type)
                        }
                        _ => {
                            return Err(ParseError::ExpectedTag {
                                expected: "field list",
                                found: args[i].tag().map(|s| s.to_string()),
                            });
                        }
                    };
                    fields.push(field);
                    i += 1;
                }
                Ok(ExternalType::Record(name, fields))
            }
            _ => {
                // Try parsing as function type
                FunctionType::from_sexp(s, _ctx).map(ExternalType::Fun)
            }
        }
    }
}

// ── Tests ──────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use super::*;
    use crate::compiler::sexpr::parse_one;
    use expect_test::expect;

    fn roundtrip<T>(expected: expect_test::Expect)
    where
        T: ToSExpr<()> + FromSExpr<()>,
    {
        let parsed = T::from_sexp(&parse_one(expected.data()).unwrap(), &mut ()).unwrap();
        expected.assert_eq(&parsed.to_sexpr(&()).to_string());
    }

    #[test]
    fn external_type_roundtrip_unit() {
        roundtrip::<ExternalType>(expect!["(unit)"]);
    }

    #[test]
    fn external_type_roundtrip_int() {
        roundtrip::<ExternalType>(expect!["(int)"]);
    }

    #[test]
    fn external_type_roundtrip_float() {
        roundtrip::<ExternalType>(expect!["(float)"]);
    }

    #[test]
    fn external_type_roundtrip_string() {
        roundtrip::<ExternalType>(expect!["(string)"]);
    }

    #[test]
    fn external_type_roundtrip_resource() {
        roundtrip::<ExternalType>(expect!["(resource my_resource)"]);
    }

    #[test]
    fn external_type_roundtrip_fun() {
        roundtrip::<ExternalType>(expect!["(fn ((x (int))) (string))"]);
    }

    #[test]
    fn external_type_roundtrip_record() {
        roundtrip::<ExternalType>(expect!["(record my_record (x (int)) (y (string)))"]);
    }

    #[test]
    fn function_type_roundtrip() {
        roundtrip::<FunctionType>(expect![r#"(fn ((x (int)) (y (float))) (string))"#]);
    }

    #[test]
    fn function_type_roundtrip_empty_params() {
        roundtrip::<FunctionType>(expect!["(fn () (int))"]);
    }
}