// Copyright (c) 2016-2021 Fabian Schuiki //! Type declarations use crate::common::errors::*; use crate::common::name::Name; use crate::common::score::{NodeRef, Result}; use crate::common::source::Spanned; use num::BigInt; use crate::add_ctx::AddContext; use crate::hir; use crate::score::*; use crate::syntax::ast; use crate::term::{Term, TermContext}; impl<'sbc, 'lazy, 'sb, 'ast, 'ctx> AddContext<'sbc, 'lazy, 'sb, 'ast, 'ctx> { /// Add a type declaration. pub fn add_type_decl(&self, decl: &'ast ast::TypeDecl) -> Result { let (mk, id, scope) = self.make(decl.span); self.ctx .define(scope, decl.name.map_into(), Def::Type(id))?; mk.lower_to_hir(Box::new(move |sbc| { let ctx = AddContext::new(sbc, scope); Ok(hir::TypeDecl { parent: scope, name: decl.name, data: ctx.add_optional(&decl.data, |ctx, d| ctx.add_type_data(id, decl.name, d))?, }) })); mk.typeck(Box::new(move |tyc| { let _hir = tyc.ctx.lazy_hir(id)?; Ok(()) })); Ok(mk.finish()) } /// Add a type definition. pub fn add_type_data( &self, id: TypeDeclRef, name: Spanned, data: &'ast Spanned, ) -> Result> { let td = match data.value { // Integer, real, and physical types. ast::RangeType(ref range_expr, ref units) => { let (dir, lb, rb) = match range_expr.data { ast::BinaryExpr( Spanned { value: ast::BinaryOp::Dir(dir), .. }, ref lb_expr, ref rb_expr, ) => { // let ctx = AddContext::new(self, self.scope); let lb = self.add_expr(lb_expr)?; let rb = self.add_expr(rb_expr)?; // let lb = ExprRef::alloc(); // let rb = ExprRef::alloc(); // self.ctx.set_ast(lb, (self.scope.into(), lb_expr.as_ref())); // self.ctx.set_ast(rb, (self.scope.into(), rb_expr.as_ref())); (dir, lb, rb) } _ => { self.emit( DiagBuilder2::error("Invalid range expression").span(range_expr.span), ); return Err(()); } }; if let Some(ref units) = *units { // Determine the primary unit. let mut prim_iter = units .iter() .enumerate() .filter(|&(_, &(_, ref expr))| expr.is_none()) .map(|(index, &(name, _))| (index, name)); let primary = match prim_iter.next() { Some(u) => u, None => { self.emit( DiagBuilder2::error(format!( "physical type `{}` has no primary unit", name.value )) .span(name.span) .add_note( "A physical type must have a primary unit of the form \ `;`. See IEEE 1076-2008 section 5.2.4.", ), ); return Err(()); } }; let mut had_fails = false; for (_, n) in prim_iter { self.emit( DiagBuilder2::error(format!( "physical type `{}` has multiple primary units", name.value )) .span(n.span) .add_note( "A physical type cannot have multiple primary units. See IEEE \ 1076-2008 section 5.2.4.", ), ); had_fails = true; } if had_fails { return Err(()); } debugln!("primary unit {:#?}", primary); // Determine the units and how they are defined with respect // to each other. let term_ctx = TermContext::new(self.ctx, self.scope); let table = units .iter() .map(|&(unit_name, ref expr)| { let rel = if let Some(ref expr) = *expr { let term = term_ctx.termify_expr(expr)?; let (value, unit) = match term.value { Term::PhysLit(value, unit) => (value, unit), _ => { self.emit( DiagBuilder2::error(format!( "`{}` is not a valid secondary unit", term.span.extract() )) .span(term.span), ); debugln!("It is a {:#?}", term.value); return Err(()); } }; if unit.value.unwrap_old().0 != id { self.emit( DiagBuilder2::error(format!( "`{}` is not a unit in the physical type `{}`", term.span.extract(), name.value )) .span(term.span) .add_note(format!( "`{}` has been declared here:", term.span.extract() )) .span(unit.span), ); } Some((value, unit.value.unwrap_old().1)) } else { None }; Ok((Spanned::new(unit_name.name, unit_name.span), rel)) }) .collect::>>() .into_iter() .collect::>>()?; // Determine the scale of each unit with respect to the // primary unit. let scale_table = table .iter() .map(|&(name, ref rel)| { let mut abs = BigInt::from(1); let mut rel_to = rel.as_ref(); while let Some(&(ref scale, index)) = rel_to { abs = abs * scale; rel_to = table[index].1.as_ref(); } (name, abs, rel.clone()) }) .collect::>(); hir::TypeData::Physical(dir, lb, rb, scale_table, primary.0) } else { hir::TypeData::Range(dir, lb, rb) } } // Enumeration types. ast::EnumType(ref elems) => { let mut lits = Vec::new(); for elem in &elems.value { // Unpack the element. Make sure it only consists of an // expression that is either an identifier or a character // literal. let lit = if !elem.choices.value.is_empty() { None } else { match elem.expr.data { ast::NameExpr(ast::CompoundName { primary: ast::PrimaryName { kind, span, .. }, ref parts, .. }) if parts.is_empty() => match kind { ast::PrimaryNameKind::Ident(n) => { Some(hir::EnumLit::Ident(Spanned::new(n, span))) } ast::PrimaryNameKind::Char(c) => { Some(hir::EnumLit::Char(Spanned::new(c, span))) } _ => None, }, _ => None, } }; // If the unpacking was successful, add the literal to the list // of enumeration literals. if let Some(lit) = lit { lits.push(lit); } else { self.emit( DiagBuilder2::error("not an enumeration literal") .span(elem.span) .add_note("expected an identifier or character literal"), ); continue; } } hir::TypeData::Enum(lits) } ast::AccessType(ref subty) => hir::TypeData::Access(self.add_subtype_ind(subty)?), ast::ArrayType(ref indices, ref elem_subty) => { // Ensure that we have at least on index, and ensure that there // are no stray choices (`expr|expr =>`) in the list. Then map // each index into its own node, unpack the element subtype, and // we're done. assert!(indices.value.len() > 0); let indices = self .ctx .sanitize_paren_elems_as_exprs(&indices.value, "an array type index") .into_iter() .map(|index| { let id = ArrayTypeIndexRef::alloc(); self.ctx.set_ast(id, (self.scope, index)); id }) .collect(); let ctx = TermContext::new(self.ctx, self.scope); let subty = ctx.termify_subtype_ind(elem_subty)?; let elem_subty = ctx.term_to_subtype_ind(subty)?.value; let elem_subty = self.add_subtype_ind_hir(elem_subty)?; hir::TypeData::Array(indices, elem_subty) } ast::FileType(ref name) => { let ctx = TermContext::new(self.ctx, self.scope); let term = ctx.termify_compound_name(name)?; let tm = ctx.term_to_type_mark(term)?; hir::TypeData::File(tm) } ast::RecordType(ref fields) => { let fields = fields .iter() .flat_map(|&(ref names, ref subty)| { let subty = self.add_subtype_ind(subty); names .iter() .map(move |name| Ok((Spanned::new(name.name, name.span), subty?))) }) .collect::>>()?; hir::TypeData::Record(fields) } ast::ProtectedType(..) => { self.emit(DiagBuilder2::fatal("protected types not implemented").span(name.span)); return Err(()); } }; Ok(Spanned::new(td, data.span)) } }