use super::types::*;
use crate::types::{Field, Function, Type, TypeEnv, TypeInner};
use crate::{pretty_parse, Error, Result};
use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
pub struct Env<'a> {
pub te: &'a mut TypeEnv,
pub pre: bool,
}
impl TypeEnv {
pub fn ast_to_type(&self, ast: &super::types::IDLType) -> Result<Type> {
let env = Env {
te: &mut self.clone(),
pre: false,
};
check_type(&env, ast)
}
}
fn check_prim(prim: &PrimType) -> Type {
match prim {
PrimType::Nat => TypeInner::Nat,
PrimType::Nat8 => TypeInner::Nat8,
PrimType::Nat16 => TypeInner::Nat16,
PrimType::Nat32 => TypeInner::Nat32,
PrimType::Nat64 => TypeInner::Nat64,
PrimType::Int => TypeInner::Int,
PrimType::Int8 => TypeInner::Int8,
PrimType::Int16 => TypeInner::Int16,
PrimType::Int32 => TypeInner::Int32,
PrimType::Int64 => TypeInner::Int64,
PrimType::Float32 => TypeInner::Float32,
PrimType::Float64 => TypeInner::Float64,
PrimType::Bool => TypeInner::Bool,
PrimType::Text => TypeInner::Text,
PrimType::Null => TypeInner::Null,
PrimType::Reserved => TypeInner::Reserved,
PrimType::Empty => TypeInner::Empty,
}
.into()
}
pub fn check_type(env: &Env, t: &IDLType) -> Result<Type> {
match t {
IDLType::PrimT(prim) => Ok(check_prim(prim)),
IDLType::VarT(id) => {
env.te.find_type(id)?;
Ok(TypeInner::Var(id.to_string()).into())
}
IDLType::OptT(t) => {
let t = check_type(env, t)?;
Ok(TypeInner::Opt(t).into())
}
IDLType::VecT(t) => {
let t = check_type(env, t)?;
Ok(TypeInner::Vec(t).into())
}
IDLType::RecordT(fs) => {
let fs = check_fields(env, fs)?;
Ok(TypeInner::Record(fs).into())
}
IDLType::VariantT(fs) => {
let fs = check_fields(env, fs)?;
Ok(TypeInner::Variant(fs).into())
}
IDLType::PrincipalT => Ok(TypeInner::Principal.into()),
IDLType::FuncT(func) => {
let mut t1 = Vec::new();
for t in func.args.iter() {
t1.push(check_type(env, t)?);
}
let mut t2 = Vec::new();
for t in func.rets.iter() {
t2.push(check_type(env, t)?);
}
if func.modes.len() > 1 {
return Err(Error::msg("cannot have more than one mode"));
}
if func.modes.len() == 1
&& func.modes[0] == crate::types::FuncMode::Oneway
&& !t2.is_empty()
{
return Err(Error::msg("oneway function has non-unit return type"));
}
let f = Function {
modes: func.modes.clone(),
args: t1,
rets: t2,
};
Ok(TypeInner::Func(f).into())
}
IDLType::ServT(ms) => {
let ms = check_meths(env, ms)?;
Ok(TypeInner::Service(ms).into())
}
IDLType::ClassT(_, _) => Err(Error::msg("service constructor not supported")),
}
}
fn check_fields(env: &Env, fs: &[TypeField]) -> Result<Vec<Field>> {
let mut res = Vec::new();
for f in fs.iter() {
let ty = check_type(env, &f.typ)?;
let field = Field {
id: f.label.clone().into(),
ty,
};
res.push(field);
}
Ok(res)
}
fn check_meths(env: &Env, ms: &[Binding]) -> Result<Vec<(String, Type)>> {
let mut res = Vec::new();
for meth in ms.iter() {
let t = check_type(env, &meth.typ)?;
if !env.pre && env.te.as_func(&t).is_err() {
return Err(Error::msg(format!(
"method {} is a non-function type",
meth.id
)));
}
res.push((meth.id.to_owned(), t));
}
Ok(res)
}
fn check_defs(env: &mut Env, decs: &[Dec]) -> Result<()> {
for dec in decs.iter() {
match dec {
Dec::TypD(Binding { id, typ }) => {
let t = check_type(env, typ)?;
env.te.0.insert(id.to_string(), t);
}
Dec::ImportD(_) => (),
}
}
Ok(())
}
fn check_cycle(env: &TypeEnv) -> Result<()> {
fn has_cycle<'a>(seen: &mut BTreeSet<&'a str>, env: &'a TypeEnv, t: &'a Type) -> Result<bool> {
match t.as_ref() {
TypeInner::Var(id) => {
if seen.insert(id) {
let ty = env.find_type(id)?;
has_cycle(seen, env, ty)
} else {
Ok(true)
}
}
_ => Ok(false),
}
}
for (id, ty) in env.0.iter() {
let mut seen = BTreeSet::new();
if has_cycle(&mut seen, env, ty)? {
return Err(Error::msg(format!("{id} has cyclic type definition")));
}
}
Ok(())
}
fn check_decs(env: &mut Env, decs: &[Dec]) -> Result<()> {
for dec in decs.iter() {
if let Dec::TypD(Binding { id, typ: _ }) = dec {
let duplicate = env.te.0.insert(id.to_string(), TypeInner::Unknown.into());
if duplicate.is_some() {
return Err(Error::msg(format!("duplicate binding for {id}")));
}
}
}
env.pre = true;
check_defs(env, decs)?;
check_cycle(env.te)?;
env.pre = false;
check_defs(env, decs)?;
Ok(())
}
fn check_actor(env: &Env, actor: &Option<IDLType>) -> Result<Option<Type>> {
match actor {
None => Ok(None),
Some(IDLType::ClassT(ts, t)) => {
let mut args = Vec::new();
for arg in ts.iter() {
args.push(check_type(env, arg)?);
}
let serv = check_type(env, t)?;
env.te.as_service(&serv)?;
Ok(Some(TypeInner::Class(args, serv).into()))
}
Some(typ) => {
let t = check_type(env, typ)?;
env.te.as_service(&t)?;
Ok(Some(t))
}
}
}
fn resolve_path(base: &Path, file: &str) -> PathBuf {
let file = PathBuf::from(file);
if file.is_absolute() {
file
} else {
base.join(file)
}
}
fn load_imports(
is_pretty: bool,
base: &Path,
visited: &mut BTreeSet<PathBuf>,
prog: &IDLProg,
list: &mut Vec<PathBuf>,
) -> Result<()> {
for dec in prog.decs.iter() {
if let Dec::ImportD(file) = dec {
let path = resolve_path(base, file);
if visited.insert(path.clone()) {
let code = std::fs::read_to_string(&path)
.map_err(|_| Error::msg(format!("Cannot import {file:?}")))?;
let code = if is_pretty {
pretty_parse::<IDLProg>(path.to_str().unwrap(), &code)?
} else {
code.parse::<IDLProg>()?
};
let base = path.parent().unwrap();
load_imports(is_pretty, base, visited, &code, list)?;
list.push(path);
}
}
}
Ok(())
}
pub fn check_prog(te: &mut TypeEnv, prog: &IDLProg) -> Result<Option<Type>> {
let mut env = Env { te, pre: false };
check_decs(&mut env, &prog.decs)?;
check_actor(&env, &prog.actor)
}
fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, Option<Type>)> {
let base = if file.is_absolute() {
file.parent().unwrap().to_path_buf()
} else {
std::env::current_dir()?
.join(file)
.parent()
.unwrap()
.to_path_buf()
};
let prog =
std::fs::read_to_string(file).map_err(|_| Error::msg(format!("Cannot open {file:?}")))?;
let prog = if is_pretty {
pretty_parse::<IDLProg>(file.to_str().unwrap(), &prog)?
} else {
prog.parse::<IDLProg>()?
};
let mut imports = Vec::new();
load_imports(is_pretty, &base, &mut BTreeSet::new(), &prog, &mut imports)?;
let mut te = TypeEnv::new();
let mut env = Env {
te: &mut te,
pre: false,
};
for import in imports.iter() {
let code = std::fs::read_to_string(import)?;
let code = code.parse::<IDLProg>()?;
check_decs(&mut env, &code.decs)?;
}
check_decs(&mut env, &prog.decs)?;
let actor = check_actor(&env, &prog.actor)?;
Ok((te, actor))
}
pub fn check_file(file: &Path) -> Result<(TypeEnv, Option<Type>)> {
check_file_(file, false)
}
pub fn pretty_check_file(file: &Path) -> Result<(TypeEnv, Option<Type>)> {
check_file_(file, true)
}