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>,
}
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)
}
}
}
}
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))
}
_ => {
FunctionType::from_sexp(s, _ctx).map(ExternalType::Fun)
}
}
}
}
#[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))"]);
}
}