use std::fmt;
use std::collections::{HashSet, HashMap};
use vec_map::{self, VecMap};
use parking_lot::RwLock;
use kailua_env::{Span, Spanned, WithLoc};
use kailua_diag::{self, Report, Reporter};
use kailua_syntax::{Str, Name};
use kailua_types::ty::{TypeContext, ClassSystemId, ClassId, Class, Display, DisplayState, Lattice};
use kailua_types::ty::{Slot, SpannedSlotSeq, Key, T, Ty, TySeq, F, Functions, Function, Nil};
use kailua_types::diag::{TypeReportMore, TypeReportHint};
use message as m;
use super::ClassSystem;
#[derive(Clone, Debug)]
enum Field {
Slot(Spanned<Slot>),
Children,
Instance,
}
#[derive(Clone, Debug)]
struct ClassDef {
generation: u32,
parent: Option<u32>,
new_ty: Option<Slot>, class_fields: HashMap<Key, Field>,
instance_fields: HashMap<Key, Field>,
}
impl ClassDef {
fn fields(&self, proto: bool) -> &HashMap<Key, Field> {
if proto { &self.class_fields } else { &self.instance_fields }
}
fn fields_mut(&mut self, proto: bool) -> &mut HashMap<Key, Field> {
if proto { &mut self.class_fields } else { &mut self.instance_fields }
}
}
struct Ancestors<'a> {
classes: &'a [ClassDef],
current: Option<u32>,
}
impl<'a> Ancestors<'a> {
fn new(classes: &'a [ClassDef], current: u32) -> Ancestors<'a> {
Ancestors { classes: classes, current: Some(current) }
}
}
impl<'a> Iterator for Ancestors<'a> {
type Item = (u32, &'a ClassDef);
fn next(&mut self) -> Option<Self::Item> {
if let Some(current) = self.current {
let cls = &self.classes[current as usize];
self.current = cls.parent;
Some((current, cls))
} else {
None
}
}
}
#[derive(Debug)]
pub struct GiderosClassSystem {
classes: RwLock<Vec<ClassDef>>,
class_names: RwLock<VecMap<Spanned<Name>>>,
}
const GENESIS_CLASS: u32 = 0;
const NEW_KEY: &'static [u8] = b"new";
const INIT_KEY: &'static [u8] = b"init";
fn is_new_key(key: &Key) -> bool {
match *key {
Key::Str(ref s) => &s[..] == NEW_KEY,
_ => false,
}
}
fn is_init_key(key: &Key) -> bool {
match *key {
Key::Str(ref s) => &s[..] == INIT_KEY,
_ => false,
}
}
impl GiderosClassSystem {
pub fn new() -> GiderosClassSystem {
GiderosClassSystem {
classes: RwLock::new(Vec::new()),
class_names: RwLock::new(VecMap::new()),
}
}
fn lookup<'a>(classes: &'a [ClassDef], cid: ClassId, proto: bool,
key: &Key) -> Option<&'a Spanned<Slot>> {
for (_, cls) in Ancestors::new(&classes, cid.1) {
let field = cls.fields(proto).get(key);
if let Some(&Field::Slot(ref info)) = field {
return Some(info);
}
}
None
}
fn new_method_from_init(classes: &[ClassDef], cid: ClassId, init: &Spanned<Slot>,
ctx: &mut TypeContext, report: &Report) -> kailua_diag::Result<Slot> {
let ty = if let Some(ty) = ctx.resolve_exact_type(&init.unlift()) {
ty
} else {
report.error(init, m::InexactInitMethod { init: init.base.display(ctx) }).done()?;
return Ok(Slot::dummy());
};
let mut func = match *ty {
T::Functions(ref func) => match **func {
Functions::Simple(ref f) => f.to_owned(),
_ => {
report.error(init, m::OverloadedFuncInitMethod { init: init.base.display(ctx) })
.done()?;
return Ok(Slot::dummy());
}
},
_ => {
report.error(init, m::NonFuncInitMethod { init: init.base.display(ctx) }).done()?;
return Ok(Slot::dummy());
},
};
let mut selfarg_ok = false;
if !func.args.head.is_empty() {
let selfarg = func.args.head.remove(0);
if let Some(selfarg) = ctx.resolve_exact_type(&selfarg) {
if selfarg.nil() == Nil::Silent {
if let T::Class(Class::Instance(cid_)) = *selfarg {
selfarg_ok =
cid.0 == cid_.0 &&
Ancestors::new(classes, cid.1).find(|&(c, _)| c == cid_.1).is_some();
}
}
}
}
if !func.argnames.is_empty() {
func.argnames.remove(0);
}
if !selfarg_ok {
report.error(init, m::BadSelfInInitMethod { init: init.base.display(ctx) }).done()?;
return Ok(Slot::dummy());
}
let returns = T::Class(Class::Instance(cid));
let ctor = Function { args: func.args, argnames: func.argnames,
returns: Some(TySeq::from(returns)) };
let ctor = Slot::new(F::Const, Ty::new(T::func(ctor)));
Ok(ctor)
}
}
impl ClassSystem for GiderosClassSystem {
fn make_class(&self, self_csid: ClassSystemId, argtys: SpannedSlotSeq, outerspan: Span,
ctx: &mut TypeContext, report: &Report) -> kailua_diag::Result<Option<ClassId>> {
if let Some(mut parent) = super::extract_parent(argtys, ctx, report)? {
if !self.classes.read().is_empty() {
parent = parent.or(Some(ClassId(self_csid, GENESIS_CLASS).without_loc()));
}
self.assume_class(self_csid, parent, outerspan, ctx, report)
} else {
Ok(None)
}
}
fn assume_class(&self, self_csid: ClassSystemId, parent: Option<Spanned<ClassId>>,
outerspan: Span, _ctx: &mut TypeContext,
report: &Report) -> kailua_diag::Result<Option<ClassId>> {
let parent = match parent {
Some(Spanned { base: ClassId(csid, cid), .. }) if csid == self_csid => Some(cid),
Some(Spanned { base: ClassId(_, _), span }) => {
report.error(span, m::ClassInheritFromDifferentClassSystem {}).done()?;
None
},
None => None,
};
let mut classes = self.classes.write();
let (parent, generation) = if let Some(parent) = parent {
assert!((parent as usize) < classes.len(), "invalid ClassId");
(Some(parent), classes[parent as usize].generation + 1)
} else if classes.is_empty() {
(None, 0)
} else {
report.error(outerspan, m::MissingParentClassForGideros {}).done()?;
(Some(GENESIS_CLASS), 1)
};
let cid = ClassId(self_csid, classes.len() as u32);
classes.push(ClassDef {
generation: generation,
parent: parent,
new_ty: None,
class_fields: HashMap::new(),
instance_fields: HashMap::new(),
});
Ok(Some(cid))
}
fn name_class(&self, cid: ClassId, name: Spanned<Name>) -> Result<(), Spanned<Name>> {
let mut names = self.class_names.write();
match names.entry(cid.1 as usize) {
vec_map::Entry::Occupied(e) => Err(e.get().clone()),
vec_map::Entry::Vacant(e) => {
info!("named {:?} as {:?}", cid, name);
e.insert(name);
Ok(())
},
}
}
fn is_subclass_of(&self, lhs: ClassId, rhs: ClassId) -> bool {
if lhs.0 != rhs.0 {
return false;
}
let classes = self.classes.read();
let lhs = lhs.1;
let rhs = rhs.1;
assert!((lhs as usize) < classes.len(), "invalid ClassId for lhs");
assert!((rhs as usize) < classes.len(), "invalid ClassId for rhs");
if lhs == rhs {
return true;
}
let lgen = classes[lhs as usize].generation;
let rgen = classes[rhs as usize].generation;
(lgen >= rgen &&
Ancestors::new(&classes, lhs).nth((lgen - rgen) as usize).map(|e| e.0) == Some(rhs))
}
fn index_rval(&self, cls: Class, key: Spanned<&Key>, expspan: Span, ctx: &mut TypeContext,
report: &Report) -> kailua_diag::Result<Option<Slot>> {
let (proto, cid) = match cls {
Class::Prototype(cid) => (true, cid),
Class::Instance(cid) => (false, cid),
};
if !proto && is_init_key(&key) {
report.error(&key, m::CannotAccessCtorThruInstance {}).done()?;
return Ok(None);
}
let mut classes = self.classes.write();
if is_new_key(&key) {
if let Some(new) = classes[cid.1 as usize].new_ty.as_ref().map(|s| s.clone()) {
Ok(Some(new))
} else {
let slot = if let Some(init) = Self::lookup(&classes, cid, true,
&Key::from(Str::from(INIT_KEY))) {
let slot = Self::new_method_from_init(&classes, cid, init, ctx, report)?;
trace!("created a new method {:?} for {:?} from the constructor {:?}",
slot, cid, init);
slot
} else {
report.error(&key, m::NoCtor {}).done()?;
Slot::dummy()
};
classes[cid.1 as usize].new_ty = Some(slot.clone());
drop(classes);
let classes = self.classes.read();
for (_, cls) in Ancestors::new(&classes, cid.1).skip(1) {
if let Some(ref parent_slot) = cls.new_ty {
if let Err(r) = slot.assert_sub(parent_slot, ctx) {
report.error(expspan,
m::NotSubtypeOfParentField {
key: &key, sub: slot.display(ctx),
sup: parent_slot.display(ctx),
})
.report_types(r, TypeReportHint::None)
.done()?;
}
}
}
Ok(Some(slot))
}
} else {
if !proto {
if let Some(info) = Self::lookup(&classes, cid, false, &key) {
return Ok(Some(info.base.clone()));
}
}
if let Some(info) = Self::lookup(&classes, cid, true, &key) {
return Ok(Some(info.base.clone()));
}
Ok(None)
}
}
fn index_lval(&self, cls: Class, key: Spanned<&Key>, expspan: Span,
hint: Option<&Slot>, ctx: &mut TypeContext,
report: &Report) -> kailua_diag::Result<Option<(bool, Slot)>> {
let (proto, cid) = match cls {
Class::Prototype(cid) => (true, cid),
Class::Instance(cid) => (false, cid),
};
if is_new_key(&key) {
report.error(expspan, m::ReservedNewMethod {}).done()?;
return Ok(None);
}
let is_init = is_init_key(&key);
if !proto && is_init {
report.error(&key, m::CannotAccessCtorThruInstance {}).done()?;
return Ok(None);
}
let mut classes = self.classes.write();
match classes[cid.1 as usize].fields(proto).get(&key) {
Some(&Field::Slot(ref slot)) => {
return Ok(Some((false, slot.base.clone())));
}
Some(&Field::Children) => {
report.error(&key, m::CannotCreateFieldDefinedInChildren { key: &key }).done()?;
return Ok(None);
}
Some(&Field::Instance) => {
report.error(&key, m::CannotCreateFieldDefinedInInstance { key: &key }).done()?;
return Ok(None);
}
None => {}
}
let slot = if let Some(hint) = hint {
let slot = hint.clone();
slot.adapt(F::Var, ctx); slot
} else {
let tvar = T::TVar(ctx.gen_tvar());
Slot::new(F::Unknown, Ty::new(tvar))
};
let mut mark_missing = |cls: &mut ClassDef, proto: bool, or_insert: Field, slot: &Slot| {
match *cls.fields_mut(proto).entry(key.base.clone()).or_insert(or_insert) {
Field::Slot(ref parent_slot) if !proto => {
Ok(Some((false, parent_slot.base.clone())))
},
Field::Slot(ref parent_slot) if !is_init => {
if let Err(r) = slot.assert_sub(parent_slot, ctx) {
report.error(expspan,
m::NotSubtypeOfParentField {
key: &key, sub: slot.display(ctx),
sup: parent_slot.base.display(ctx),
})
.note_if(parent_slot, m::PreviousParentFieldType {})
.report_types(r, TypeReportHint::None)
.done()?;
}
Ok(None)
},
Field::Slot(_) => Ok(None),
Field::Children | Field::Instance => Ok(None),
}
};
if !proto {
if let Some(slot) = mark_missing(&mut classes[cid.1 as usize], true,
Field::Instance, &slot)? {
return Ok(Some(slot));
}
}
let mut parent_cid = classes[cid.1 as usize].parent;
while let Some(cid) = parent_cid {
let cls = &mut classes[cid as usize];
if let Some(slot) = mark_missing(cls, false, Field::Children, &slot)? {
return Ok(Some(slot));
}
if let Some(slot) = mark_missing(cls, true, Field::Children, &slot)? {
return Ok(Some(slot));
}
parent_cid = cls.parent;
}
let fields = classes[cid.1 as usize].fields_mut(proto);
fields.insert(key.base.clone(), Field::Slot(slot.clone().with_loc(&key)));
Ok(Some((true, slot)))
}
fn fmt_class(&self, cid: ClassId, f: &mut fmt::Formatter, st: &DisplayState) -> fmt::Result {
let names = self.class_names.read();
match (&st.locale[..], &names.get(cid.1 as usize)) {
(_, &Some(ref name)) => write!(f, "{:+}", name),
("ko", &None) => write!(f, "<이름 없는 클래스 #{}.{}>", (cid.0).0, cid.1),
(_, &None) => write!(f, "<unnamed class #{}.{}>", (cid.0).0, cid.1),
}
}
fn list_fields(&self, cls: Class,
f: &mut FnMut(&Key, &Slot) -> Result<(), ()>) -> Result<(), ()> {
let (proto, cid) = match cls {
Class::Prototype(cid) => (true, cid),
Class::Instance(cid) => (false, cid),
};
let classes = self.classes.read();
let mut seen = HashSet::new();
let mut list = |proto| {
for (_, cls) in Ancestors::new(&classes, cid.1) {
for (key, slot) in cls.fields(proto) {
if let Field::Slot(ref slot) = *slot {
if seen.insert(key) {
f(key, slot)?;
}
}
}
}
Ok(())
};
if !proto {
list(false)?;
}
list(true)
}
fn list_parents(&self, cid: ClassId,
f: &mut FnMut(ClassId) -> Result<(), ()>) -> Result<(), ()> {
let self_csid = cid.0;
if let Some(cid) = self.classes.read()[cid.1 as usize].parent {
f(ClassId(self_csid, cid))?;
}
Ok(())
}
}