| //! Semantic analysis. |
| //! |
| //! This module primarily contains the type environment and term environment. |
| //! |
| //! The type environment is constructed by analyzing an input AST. The type |
| //! environment records the types used in the input source and the types of our |
| //! various rules and symbols. ISLE's type system is intentionally easy to |
| //! check, only requires a single pass over the AST, and doesn't require any |
| //! unification or anything like that. |
| //! |
| //! The term environment is constructed from both the AST and type |
| //! environment. It is sort of a typed and reorganized AST that more directly |
| //! reflects ISLE semantics than the input ISLE source code (where as the AST is |
| //! the opposite). |
| |
| use crate::ast; |
| use crate::error::*; |
| use crate::lexer::Pos; |
| use crate::log; |
| use crate::stablemapset::{StableMap, StableSet}; |
| use std::collections::hash_map::Entry; |
| use std::collections::BTreeMap; |
| use std::collections::BTreeSet; |
| use std::collections::HashMap; |
| use std::sync::Arc; |
| |
| declare_id!( |
| /// The id of an interned symbol. |
| Sym |
| ); |
| declare_id!( |
| /// The id of an interned type inside the `TypeEnv`. |
| TypeId |
| ); |
| declare_id!( |
| /// The id of a variant inside an enum. |
| VariantId |
| ); |
| declare_id!( |
| /// The id of a field inside a variant. |
| FieldId |
| ); |
| declare_id!( |
| /// The id of an interned term inside the `TermEnv`. |
| TermId |
| ); |
| declare_id!( |
| /// The id of an interned rule inside the `TermEnv`. |
| RuleId |
| ); |
| declare_id!( |
| /// The id of a bound variable inside a `Bindings`. |
| VarId |
| ); |
| |
| /// The type environment. |
| /// |
| /// Keeps track of which symbols and rules have which types. |
| #[derive(Debug)] |
| pub struct TypeEnv { |
| /// Arena of input ISLE source filenames. |
| /// |
| /// We refer to these indirectly through the `Pos::file` indices. |
| pub filenames: Vec<Arc<str>>, |
| |
| /// Arena of input ISLE source contents. |
| /// |
| /// We refer to these indirectly through the `Pos::file` indices. |
| pub file_texts: Vec<Arc<str>>, |
| |
| /// Arena of interned symbol names. |
| /// |
| /// Referred to indirectly via `Sym` indices. |
| pub syms: Vec<String>, |
| |
| /// Map of already-interned symbol names to their `Sym` ids. |
| pub sym_map: StableMap<String, Sym>, |
| |
| /// Arena of type definitions. |
| /// |
| /// Referred to indirectly via `TypeId`s. |
| pub types: Vec<Type>, |
| |
| /// A map from a type name symbol to its `TypeId`. |
| pub type_map: StableMap<Sym, TypeId>, |
| |
| /// The types of constant symbols. |
| pub const_types: StableMap<Sym, TypeId>, |
| |
| /// Type errors that we've found so far during type checking. |
| pub errors: Vec<Error>, |
| } |
| |
| /// A type. |
| #[derive(Clone, Debug, PartialEq, Eq)] |
| pub enum Type { |
| /// A primitive, `Copy` type. |
| /// |
| /// These are always defined externally, and we allow literals of these |
| /// types to pass through from ISLE source code to the emitted Rust code. |
| Primitive(TypeId, Sym, Pos), |
| |
| /// A sum type. |
| /// |
| /// Note that enums with only one variant are equivalent to a "struct". |
| Enum { |
| /// The name of this enum. |
| name: Sym, |
| /// This `enum`'s type id. |
| id: TypeId, |
| /// Is this `enum` defined in external Rust code? |
| /// |
| /// If so, ISLE will not emit a definition for it. If not, then it will |
| /// emit a Rust definition for it. |
| is_extern: bool, |
| /// Whether this type should *not* derive `Debug`. |
| /// |
| /// Incompatible with `is_extern`. |
| is_nodebug: bool, |
| /// The different variants for this enum. |
| variants: Vec<Variant>, |
| /// The ISLE source position where this `enum` is defined. |
| pos: Pos, |
| }, |
| } |
| |
| impl Type { |
| /// Get the name of this `Type`. |
| pub fn name<'a>(&self, tyenv: &'a TypeEnv) -> &'a str { |
| match self { |
| Self::Primitive(_, name, _) | Self::Enum { name, .. } => &tyenv.syms[name.index()], |
| } |
| } |
| |
| /// Get the position where this type was defined. |
| pub fn pos(&self) -> Pos { |
| match self { |
| Self::Primitive(_, _, pos) | Self::Enum { pos, .. } => *pos, |
| } |
| } |
| |
| /// Is this a primitive type? |
| pub fn is_prim(&self) -> bool { |
| matches!(self, Type::Primitive(..)) |
| } |
| } |
| |
| /// A variant of an enum. |
| #[derive(Clone, Debug, PartialEq, Eq)] |
| pub struct Variant { |
| /// The name of this variant. |
| pub name: Sym, |
| |
| /// The full, prefixed-with-the-enum's-name name of this variant. |
| /// |
| /// E.g. if the enum is `Foo` and this variant is `Bar`, then the |
| /// `fullname` is `Foo.Bar`. |
| pub fullname: Sym, |
| |
| /// The id of this variant, i.e. the index of this variant within its |
| /// enum's `Type::Enum::variants`. |
| pub id: VariantId, |
| |
| /// The data fields of this enum variant. |
| pub fields: Vec<Field>, |
| } |
| |
| /// A field of a `Variant`. |
| #[derive(Clone, Debug, PartialEq, Eq)] |
| pub struct Field { |
| /// The name of this field. |
| pub name: Sym, |
| /// This field's id. |
| pub id: FieldId, |
| /// The type of this field. |
| pub ty: TypeId, |
| } |
| |
| /// The term environment. |
| /// |
| /// This is sort of a typed and reorganized AST that more directly reflects ISLE |
| /// semantics than the input ISLE source code (where as the AST is the |
| /// opposite). |
| #[derive(Clone, Debug)] |
| pub struct TermEnv { |
| /// Arena of interned terms defined in this ISLE program. |
| /// |
| /// This is indexed by `TermId`. |
| pub terms: Vec<Term>, |
| |
| /// A map from am interned `Term`'s name to its `TermId`. |
| pub term_map: StableMap<Sym, TermId>, |
| |
| /// Arena of interned rules defined in this ISLE program. |
| /// |
| /// This is indexed by `RuleId`. |
| pub rules: Vec<Rule>, |
| |
| /// Map from (inner_ty, outer_ty) pairs to term IDs, giving the |
| /// defined implicit type-converter terms we can try to use to fit |
| /// types together. |
| pub converters: StableMap<(TypeId, TypeId), TermId>, |
| } |
| |
| /// A term. |
| /// |
| /// Maps parameter types to result types if this is a constructor term, or |
| /// result types to parameter types if this is an extractor term. Or both if |
| /// this term can be either a constructor or an extractor. |
| #[derive(Clone, Debug, PartialEq, Eq)] |
| pub struct Term { |
| /// This term's id. |
| pub id: TermId, |
| /// The source position where this term was declared. |
| pub decl_pos: Pos, |
| /// The name of this term. |
| pub name: Sym, |
| /// The parameter types to this term. |
| pub arg_tys: Vec<TypeId>, |
| /// The result types of this term. |
| pub ret_ty: TypeId, |
| /// The kind of this term. |
| pub kind: TermKind, |
| } |
| |
| /// Flags from a term's declaration with `(decl ...)`. |
| #[derive(Clone, Debug, PartialEq, Eq)] |
| pub struct TermFlags { |
| /// Whether the term is marked as `pure`. |
| pub pure: bool, |
| /// Whether the term is marked as `multi`. |
| pub multi: bool, |
| /// Whether the term is marked as `partial`. |
| pub partial: bool, |
| } |
| |
| /// The kind of a term. |
| #[derive(Clone, Debug, PartialEq, Eq)] |
| pub enum TermKind { |
| /// An enum variant constructor or extractor. |
| EnumVariant { |
| /// Which variant of the enum: e.g. for enum type `A` if a term is |
| /// `(A.A1 ...)` then the variant ID corresponds to `A1`. |
| variant: VariantId, |
| }, |
| /// A term declared via a `(decl ...)` form. |
| Decl { |
| /// Flags from the term's declaration. |
| flags: TermFlags, |
| /// The kind of this term's constructor, if any. |
| constructor_kind: Option<ConstructorKind>, |
| /// The kind of this term's extractor, if any. |
| extractor_kind: Option<ExtractorKind>, |
| }, |
| } |
| |
| /// The kind of a constructor for a term. |
| #[derive(Clone, Debug, PartialEq, Eq)] |
| pub enum ConstructorKind { |
| /// A term with "internal" rules that work in the forward direction. Becomes |
| /// a compiled Rust function in the generated code. |
| InternalConstructor, |
| /// A term defined solely by an external constructor function. |
| ExternalConstructor { |
| /// The external name of the constructor function. |
| name: Sym, |
| }, |
| } |
| |
| /// The kind of an extractor for a term. |
| #[derive(Clone, Debug, PartialEq, Eq)] |
| pub enum ExtractorKind { |
| /// A term that defines an "extractor macro" in the LHS of a pattern. Its |
| /// arguments take patterns and are simply substituted with the given |
| /// patterns when used. |
| InternalExtractor { |
| /// This extractor's pattern. |
| template: ast::Pattern, |
| }, |
| /// A term defined solely by an external extractor function. |
| ExternalExtractor { |
| /// The external name of the extractor function. |
| name: Sym, |
| /// Is the external extractor infallible? |
| infallible: bool, |
| /// The position where this external extractor was declared. |
| pos: Pos, |
| }, |
| } |
| |
| /// How many values a function can return. |
| #[derive(Clone, Copy, Debug, Eq, PartialEq)] |
| pub enum ReturnKind { |
| /// Exactly one return value. |
| Plain, |
| /// Zero or one return values. |
| Option, |
| /// Zero or more return values. |
| Iterator, |
| } |
| |
| /// An external function signature. |
| #[derive(Clone, Debug)] |
| pub struct ExternalSig { |
| /// The name of the external function. |
| pub func_name: String, |
| /// The name of the external function, prefixed with the context trait. |
| pub full_name: String, |
| /// The types of this function signature's parameters. |
| pub param_tys: Vec<TypeId>, |
| /// The types of this function signature's results. |
| pub ret_tys: Vec<TypeId>, |
| /// How many values can this function return? |
| pub ret_kind: ReturnKind, |
| } |
| |
| impl Term { |
| /// Get this term's type. |
| pub fn ty(&self) -> TypeId { |
| self.ret_ty |
| } |
| |
| fn check_args_count<T>(&self, args: &[T], tyenv: &mut TypeEnv, pos: Pos, sym: &ast::Ident) { |
| if self.arg_tys.len() != args.len() { |
| tyenv.report_error( |
| pos, |
| format!( |
| "Incorrect argument count for term '{}': got {}, expect {}", |
| sym.0, |
| args.len(), |
| self.arg_tys.len() |
| ), |
| ); |
| } |
| } |
| |
| /// Is this term an enum variant? |
| pub fn is_enum_variant(&self) -> bool { |
| matches!(self.kind, TermKind::EnumVariant { .. }) |
| } |
| |
| /// Is this term partial? |
| pub fn is_partial(&self) -> bool { |
| matches!( |
| self.kind, |
| TermKind::Decl { |
| flags: TermFlags { partial: true, .. }, |
| .. |
| } |
| ) |
| } |
| |
| /// Does this term have a constructor? |
| pub fn has_constructor(&self) -> bool { |
| matches!( |
| self.kind, |
| TermKind::EnumVariant { .. } |
| | TermKind::Decl { |
| constructor_kind: Some(_), |
| .. |
| } |
| ) |
| } |
| |
| /// Does this term have an extractor? |
| pub fn has_extractor(&self) -> bool { |
| matches!( |
| self.kind, |
| TermKind::EnumVariant { .. } |
| | TermKind::Decl { |
| extractor_kind: Some(_), |
| .. |
| } |
| ) |
| } |
| |
| /// Is this term's extractor external? |
| pub fn has_external_extractor(&self) -> bool { |
| matches!( |
| self.kind, |
| TermKind::Decl { |
| extractor_kind: Some(ExtractorKind::ExternalExtractor { .. }), |
| .. |
| } |
| ) |
| } |
| |
| /// Is this term's constructor external? |
| pub fn has_external_constructor(&self) -> bool { |
| matches!( |
| self.kind, |
| TermKind::Decl { |
| constructor_kind: Some(ConstructorKind::ExternalConstructor { .. }), |
| .. |
| } |
| ) |
| } |
| |
| /// Get this term's extractor's external function signature, if any. |
| pub fn extractor_sig(&self, tyenv: &TypeEnv) -> Option<ExternalSig> { |
| match &self.kind { |
| TermKind::Decl { |
| flags, |
| extractor_kind: |
| Some(ExtractorKind::ExternalExtractor { |
| name, infallible, .. |
| }), |
| .. |
| } => { |
| let ret_kind = if flags.multi { |
| ReturnKind::Iterator |
| } else if *infallible { |
| ReturnKind::Plain |
| } else { |
| ReturnKind::Option |
| }; |
| Some(ExternalSig { |
| func_name: tyenv.syms[name.index()].clone(), |
| full_name: format!("C::{}", tyenv.syms[name.index()]), |
| param_tys: vec![self.ret_ty], |
| ret_tys: self.arg_tys.clone(), |
| ret_kind, |
| }) |
| } |
| _ => None, |
| } |
| } |
| |
| /// Get this term's constructor's external function signature, if any. |
| pub fn constructor_sig(&self, tyenv: &TypeEnv) -> Option<ExternalSig> { |
| match &self.kind { |
| TermKind::Decl { |
| constructor_kind: Some(kind), |
| flags, |
| .. |
| } => { |
| let (func_name, full_name) = match kind { |
| ConstructorKind::InternalConstructor => { |
| let name = format!("constructor_{}", tyenv.syms[self.name.index()]); |
| (name.clone(), name) |
| } |
| ConstructorKind::ExternalConstructor { name } => ( |
| tyenv.syms[name.index()].clone(), |
| format!("C::{}", tyenv.syms[name.index()]), |
| ), |
| }; |
| let ret_kind = if flags.multi { |
| ReturnKind::Iterator |
| } else if flags.partial { |
| ReturnKind::Option |
| } else { |
| ReturnKind::Plain |
| }; |
| Some(ExternalSig { |
| func_name, |
| full_name, |
| param_tys: self.arg_tys.clone(), |
| ret_tys: vec![self.ret_ty], |
| ret_kind, |
| }) |
| } |
| _ => None, |
| } |
| } |
| } |
| |
| /// A term rewrite rule. |
| #[derive(Clone, Debug)] |
| pub struct Rule { |
| /// This rule's id. |
| pub id: RuleId, |
| /// The left-hand side pattern that this rule matches. |
| pub root_term: TermId, |
| /// Patterns to test against the root term's arguments. |
| pub args: Vec<Pattern>, |
| /// Any subpattern "if-let" clauses. |
| pub iflets: Vec<IfLet>, |
| /// The right-hand side expression that this rule evaluates upon successful |
| /// match. |
| pub rhs: Expr, |
| /// Variable names used in this rule, indexed by [VarId]. |
| pub vars: Vec<BoundVar>, |
| /// The priority of this rule, defaulted to 0 if it was missing in the source. |
| pub prio: i64, |
| /// The source position where this rule is defined. |
| pub pos: Pos, |
| } |
| |
| /// A name bound in a pattern or let-expression. |
| #[derive(Clone, Debug)] |
| pub struct BoundVar { |
| /// The identifier used for this variable within the scope of the current [Rule]. |
| pub id: VarId, |
| /// The variable's name. |
| pub name: Sym, |
| /// The type of the value this variable is bound to. |
| pub ty: TypeId, |
| /// A counter used to check whether this variable is still in scope during |
| /// semantic analysis. Not meaningful afterward. |
| scope: usize, |
| } |
| |
| /// An `if-let` clause with a subpattern match on an expr after the |
| /// main LHS matches. |
| #[derive(Clone, Debug)] |
| pub struct IfLet { |
| /// The left-hand side pattern that this `if-let` clause matches |
| /// against the expression below. |
| pub lhs: Pattern, |
| /// The right-hand side expression that this pattern |
| /// evaluates. Must be pure. |
| pub rhs: Expr, |
| } |
| |
| /// A left-hand side pattern of some rule. |
| #[derive(Clone, Debug, PartialEq, Eq)] |
| pub enum Pattern { |
| /// Bind a variable of the given type from the current value. |
| /// |
| /// Keep matching on the value with the subpattern. |
| BindPattern(TypeId, VarId, Box<Pattern>), |
| |
| /// Match the current value against an already bound variable with the given |
| /// type. |
| Var(TypeId, VarId), |
| |
| /// Match the current value against a constant integer of the given integer |
| /// type. |
| ConstInt(TypeId, i128), |
| |
| /// Match the current value against a constant primitive value of the given |
| /// primitive type. |
| ConstPrim(TypeId, Sym), |
| |
| /// Match the current value against the given extractor term with the given |
| /// arguments. |
| Term(TypeId, TermId, Vec<Pattern>), |
| |
| /// Match anything of the given type successfully. |
| Wildcard(TypeId), |
| |
| /// Match all of the following patterns of the given type. |
| And(TypeId, Vec<Pattern>), |
| } |
| |
| /// A right-hand side expression of some rule. |
| #[derive(Clone, Debug, PartialEq, Eq)] |
| pub enum Expr { |
| /// Invoke this term constructor with the given arguments. |
| Term(TypeId, TermId, Vec<Expr>), |
| /// Get the value of a variable that was bound in the left-hand side. |
| Var(TypeId, VarId), |
| /// Get a constant integer. |
| ConstInt(TypeId, i128), |
| /// Get a constant primitive. |
| ConstPrim(TypeId, Sym), |
| /// Evaluate the nested expressions and bind their results to the given |
| /// variables, then evaluate the body expression. |
| Let { |
| /// The type of the result of this let expression. |
| ty: TypeId, |
| /// The expressions that are evaluated and bound to the given variables. |
| bindings: Vec<(VarId, TypeId, Box<Expr>)>, |
| /// The body expression that is evaluated after the bindings. |
| body: Box<Expr>, |
| }, |
| } |
| |
| /// Visitor interface for [Pattern]s. Visitors can assign an arbitrary identifier to each |
| /// subpattern, which is threaded through to subsequent calls into the visitor. |
| pub trait PatternVisitor { |
| /// The type of subpattern identifiers. |
| type PatternId: Copy; |
| |
| /// Match if `a` and `b` have equal values. |
| fn add_match_equal(&mut self, a: Self::PatternId, b: Self::PatternId, ty: TypeId); |
| /// Match if `input` is the given integer constant. |
| fn add_match_int(&mut self, input: Self::PatternId, ty: TypeId, int_val: i128); |
| /// Match if `input` is the given primitive constant. |
| fn add_match_prim(&mut self, input: Self::PatternId, ty: TypeId, val: Sym); |
| |
| /// Match if `input` is the given enum variant. Returns an identifier for each field within the |
| /// enum variant. The length of the return list must equal the length of `arg_tys`. |
| fn add_match_variant( |
| &mut self, |
| input: Self::PatternId, |
| input_ty: TypeId, |
| arg_tys: &[TypeId], |
| variant: VariantId, |
| ) -> Vec<Self::PatternId>; |
| |
| /// Match if the given external extractor succeeds on `input`. Returns an identifier for each |
| /// return value from the external extractor. The length of the return list must equal the |
| /// length of `output_tys`. |
| fn add_extract( |
| &mut self, |
| input: Self::PatternId, |
| input_ty: TypeId, |
| output_tys: Vec<TypeId>, |
| term: TermId, |
| infallible: bool, |
| multi: bool, |
| ) -> Vec<Self::PatternId>; |
| } |
| |
| impl Pattern { |
| /// Get this pattern's type. |
| pub fn ty(&self) -> TypeId { |
| match *self { |
| Self::BindPattern(t, ..) => t, |
| Self::Var(t, ..) => t, |
| Self::ConstInt(t, ..) => t, |
| Self::ConstPrim(t, ..) => t, |
| Self::Term(t, ..) => t, |
| Self::Wildcard(t, ..) => t, |
| Self::And(t, ..) => t, |
| } |
| } |
| |
| /// Recursively visit every sub-pattern. |
| pub fn visit<V: PatternVisitor>( |
| &self, |
| visitor: &mut V, |
| input: V::PatternId, |
| termenv: &TermEnv, |
| vars: &mut HashMap<VarId, V::PatternId>, |
| ) { |
| match *self { |
| Pattern::BindPattern(_ty, var, ref subpat) => { |
| // Bind the appropriate variable and recurse. |
| assert!(!vars.contains_key(&var)); |
| vars.insert(var, input); |
| subpat.visit(visitor, input, termenv, vars); |
| } |
| Pattern::Var(ty, var) => { |
| // Assert that the value matches the existing bound var. |
| let var_val = vars |
| .get(&var) |
| .copied() |
| .expect("Variable should already be bound"); |
| visitor.add_match_equal(input, var_val, ty); |
| } |
| Pattern::ConstInt(ty, value) => visitor.add_match_int(input, ty, value), |
| Pattern::ConstPrim(ty, value) => visitor.add_match_prim(input, ty, value), |
| Pattern::Term(ty, term, ref args) => { |
| // Determine whether the term has an external extractor or not. |
| let termdata = &termenv.terms[term.index()]; |
| let arg_values = match &termdata.kind { |
| TermKind::EnumVariant { variant } => { |
| visitor.add_match_variant(input, ty, &termdata.arg_tys, *variant) |
| } |
| TermKind::Decl { |
| extractor_kind: None, |
| .. |
| } => { |
| panic!("Pattern invocation of undefined term body") |
| } |
| TermKind::Decl { |
| extractor_kind: Some(ExtractorKind::InternalExtractor { .. }), |
| .. |
| } => { |
| panic!("Should have been expanded away") |
| } |
| TermKind::Decl { |
| flags, |
| extractor_kind: Some(ExtractorKind::ExternalExtractor { infallible, .. }), |
| .. |
| } => { |
| // Evaluate all `input` args. |
| let output_tys = args.iter().map(|arg| arg.ty()).collect(); |
| |
| // Invoke the extractor. |
| visitor.add_extract( |
| input, |
| termdata.ret_ty, |
| output_tys, |
| term, |
| *infallible && !flags.multi, |
| flags.multi, |
| ) |
| } |
| }; |
| for (pat, val) in args.iter().zip(arg_values) { |
| pat.visit(visitor, val, termenv, vars); |
| } |
| } |
| Pattern::And(_ty, ref children) => { |
| for child in children { |
| child.visit(visitor, input, termenv, vars); |
| } |
| } |
| Pattern::Wildcard(_ty) => { |
| // Nothing! |
| } |
| } |
| } |
| } |
| |
| /// Visitor interface for [Expr]s. Visitors can return an arbitrary identifier for each |
| /// subexpression, which is threaded through to subsequent calls into the visitor. |
| pub trait ExprVisitor { |
| /// The type of subexpression identifiers. |
| type ExprId: Copy; |
| |
| /// Construct a constant integer. |
| fn add_const_int(&mut self, ty: TypeId, val: i128) -> Self::ExprId; |
| /// Construct a primitive constant. |
| fn add_const_prim(&mut self, ty: TypeId, val: Sym) -> Self::ExprId; |
| |
| /// Construct an enum variant with the given `inputs` assigned to the variant's fields in order. |
| fn add_create_variant( |
| &mut self, |
| inputs: Vec<(Self::ExprId, TypeId)>, |
| ty: TypeId, |
| variant: VariantId, |
| ) -> Self::ExprId; |
| |
| /// Call an external constructor with the given `inputs` as arguments. |
| fn add_construct( |
| &mut self, |
| inputs: Vec<(Self::ExprId, TypeId)>, |
| ty: TypeId, |
| term: TermId, |
| pure: bool, |
| infallible: bool, |
| multi: bool, |
| ) -> Self::ExprId; |
| } |
| |
| impl Expr { |
| /// Get this expression's type. |
| pub fn ty(&self) -> TypeId { |
| match *self { |
| Self::Term(t, ..) => t, |
| Self::Var(t, ..) => t, |
| Self::ConstInt(t, ..) => t, |
| Self::ConstPrim(t, ..) => t, |
| Self::Let { ty: t, .. } => t, |
| } |
| } |
| |
| /// Recursively visit every subexpression. |
| pub fn visit<V: ExprVisitor>( |
| &self, |
| visitor: &mut V, |
| termenv: &TermEnv, |
| vars: &HashMap<VarId, V::ExprId>, |
| ) -> V::ExprId { |
| log!("Expr::visit: expr {:?}", self); |
| match *self { |
| Expr::ConstInt(ty, val) => visitor.add_const_int(ty, val), |
| Expr::ConstPrim(ty, val) => visitor.add_const_prim(ty, val), |
| Expr::Let { |
| ty: _ty, |
| ref bindings, |
| ref body, |
| } => { |
| let mut vars = vars.clone(); |
| for &(var, _var_ty, ref var_expr) in bindings { |
| let var_value = var_expr.visit(visitor, termenv, &vars); |
| vars.insert(var, var_value); |
| } |
| body.visit(visitor, termenv, &vars) |
| } |
| Expr::Var(_ty, var_id) => *vars.get(&var_id).unwrap(), |
| Expr::Term(ty, term, ref arg_exprs) => { |
| let termdata = &termenv.terms[term.index()]; |
| let arg_values_tys = arg_exprs |
| .iter() |
| .map(|arg_expr| arg_expr.visit(visitor, termenv, vars)) |
| .zip(termdata.arg_tys.iter().copied()) |
| .collect(); |
| match &termdata.kind { |
| TermKind::EnumVariant { variant } => { |
| visitor.add_create_variant(arg_values_tys, ty, *variant) |
| } |
| TermKind::Decl { |
| constructor_kind: Some(_), |
| flags, |
| .. |
| } => { |
| visitor.add_construct( |
| arg_values_tys, |
| ty, |
| term, |
| flags.pure, |
| /* infallible = */ !flags.partial, |
| flags.multi, |
| ) |
| } |
| TermKind::Decl { |
| constructor_kind: None, |
| .. |
| } => panic!("Should have been caught by typechecking"), |
| } |
| } |
| } |
| } |
| |
| fn visit_in_rule<V: RuleVisitor>( |
| &self, |
| visitor: &mut V, |
| termenv: &TermEnv, |
| vars: &HashMap<VarId, <V::PatternVisitor as PatternVisitor>::PatternId>, |
| ) -> V::Expr { |
| let var_exprs = vars |
| .iter() |
| .map(|(&var, &val)| (var, visitor.pattern_as_expr(val))) |
| .collect(); |
| visitor.add_expr(|visitor| VisitedExpr { |
| ty: self.ty(), |
| value: self.visit(visitor, termenv, &var_exprs), |
| }) |
| } |
| } |
| |
| /// Information about an expression after it has been fully visited in [RuleVisitor::add_expr]. |
| #[derive(Clone, Copy)] |
| pub struct VisitedExpr<V: ExprVisitor> { |
| /// The type of the top-level expression. |
| pub ty: TypeId, |
| /// The identifier returned by the visitor for the top-level expression. |
| pub value: V::ExprId, |
| } |
| |
| /// Visitor interface for [Rule]s. Visitors must be able to visit patterns by implementing |
| /// [PatternVisitor], and to visit expressions by providing a type that implements [ExprVisitor]. |
| pub trait RuleVisitor { |
| /// The type of pattern visitors constructed by [RuleVisitor::add_pattern]. |
| type PatternVisitor: PatternVisitor; |
| /// The type of expression visitors constructed by [RuleVisitor::add_expr]. |
| type ExprVisitor: ExprVisitor; |
| /// The type returned from [RuleVisitor::add_expr], which may be exchanged for a subpattern |
| /// identifier using [RuleVisitor::expr_as_pattern]. |
| type Expr; |
| |
| /// Visit one of the arguments to the top-level pattern. |
| fn add_arg( |
| &mut self, |
| index: usize, |
| ty: TypeId, |
| ) -> <Self::PatternVisitor as PatternVisitor>::PatternId; |
| |
| /// Visit a pattern, used once for the rule's left-hand side and once for each if-let. You can |
| /// determine which part of the rule the pattern comes from based on whether the `PatternId` |
| /// passed to the first call to this visitor came from `add_arg` or `expr_as_pattern`. |
| fn add_pattern<F>(&mut self, visitor: F) |
| where |
| F: FnOnce(&mut Self::PatternVisitor); |
| |
| /// Visit an expression, used once for each if-let and once for the rule's right-hand side. |
| fn add_expr<F>(&mut self, visitor: F) -> Self::Expr |
| where |
| F: FnOnce(&mut Self::ExprVisitor) -> VisitedExpr<Self::ExprVisitor>; |
| |
| /// Given an expression from [RuleVisitor::add_expr], return an identifier that can be used with |
| /// a pattern visitor in [RuleVisitor::add_pattern]. |
| fn expr_as_pattern( |
| &mut self, |
| expr: Self::Expr, |
| ) -> <Self::PatternVisitor as PatternVisitor>::PatternId; |
| |
| /// Given an identifier from the pattern visitor, return an identifier that can be used with |
| /// the expression visitor. |
| fn pattern_as_expr( |
| &mut self, |
| pattern: <Self::PatternVisitor as PatternVisitor>::PatternId, |
| ) -> <Self::ExprVisitor as ExprVisitor>::ExprId; |
| } |
| |
| impl Rule { |
| /// Recursively visit every pattern and expression in this rule. Returns the [RuleVisitor::Expr] |
| /// that was returned from [RuleVisitor::add_expr] when that function was called on the rule's |
| /// right-hand side. |
| pub fn visit<V: RuleVisitor>(&self, visitor: &mut V, termenv: &TermEnv) -> V::Expr { |
| let mut vars = HashMap::new(); |
| |
| // Visit the pattern, starting from the root input value. |
| let termdata = &termenv.terms[self.root_term.index()]; |
| for (i, (subpat, &arg_ty)) in self.args.iter().zip(termdata.arg_tys.iter()).enumerate() { |
| let value = visitor.add_arg(i, arg_ty); |
| visitor.add_pattern(|visitor| subpat.visit(visitor, value, termenv, &mut vars)); |
| } |
| |
| // Visit the `if-let` clauses, using `V::ExprVisitor` for the sub-exprs (right-hand sides). |
| for iflet in self.iflets.iter() { |
| let subexpr = iflet.rhs.visit_in_rule(visitor, termenv, &vars); |
| let value = visitor.expr_as_pattern(subexpr); |
| visitor.add_pattern(|visitor| iflet.lhs.visit(visitor, value, termenv, &mut vars)); |
| } |
| |
| // Visit the rule's right-hand side, making use of the bound variables from the pattern. |
| self.rhs.visit_in_rule(visitor, termenv, &vars) |
| } |
| } |
| |
| /// Given an `Option<T>`, unwrap the inner `T` value, or `continue` if it is |
| /// `None`. |
| /// |
| /// Useful for when we encountered an error earlier in our analysis but kept |
| /// going to find more errors, and now we've run into some missing data that |
| /// would have been filled in if we didn't hit that original error, but we want |
| /// to keep going to find more errors. |
| macro_rules! unwrap_or_continue { |
| ($e:expr) => { |
| match $e { |
| Some(x) => x, |
| None => continue, |
| } |
| }; |
| } |
| |
| impl TypeEnv { |
| /// Construct the type environment from the AST. |
| pub fn from_ast(defs: &ast::Defs) -> Result<TypeEnv, Errors> { |
| let mut tyenv = TypeEnv { |
| filenames: defs.filenames.clone(), |
| file_texts: defs.file_texts.clone(), |
| syms: vec![], |
| sym_map: StableMap::new(), |
| types: vec![], |
| type_map: StableMap::new(), |
| const_types: StableMap::new(), |
| errors: vec![], |
| }; |
| |
| // Traverse defs, assigning type IDs to type names. We'll fill |
| // in types on a second pass. |
| for def in &defs.defs { |
| match def { |
| &ast::Def::Type(ref td) => { |
| let tid = TypeId(tyenv.type_map.len()); |
| let name = tyenv.intern_mut(&td.name); |
| |
| if let Some(existing) = tyenv.type_map.get(&name).copied() { |
| tyenv.report_error( |
| td.pos, |
| format!("Type with name '{}' defined more than once", td.name.0), |
| ); |
| let pos = unwrap_or_continue!(tyenv.types.get(existing.index())).pos(); |
| tyenv.report_error( |
| pos, |
| format!("Type with name '{}' already defined here", td.name.0), |
| ); |
| continue; |
| } |
| |
| tyenv.type_map.insert(name, tid); |
| } |
| _ => {} |
| } |
| } |
| |
| // Now lower AST nodes to type definitions, raising errors |
| // where typenames of fields are undefined or field names are |
| // duplicated. |
| for def in &defs.defs { |
| match def { |
| &ast::Def::Type(ref td) => { |
| let tid = tyenv.types.len(); |
| if let Some(ty) = tyenv.type_from_ast(TypeId(tid), td) { |
| tyenv.types.push(ty); |
| } |
| } |
| _ => {} |
| } |
| } |
| |
| // Now collect types for extern constants. |
| for def in &defs.defs { |
| if let &ast::Def::Extern(ast::Extern::Const { |
| ref name, |
| ref ty, |
| pos, |
| }) = def |
| { |
| let ty = match tyenv.get_type_by_name(ty) { |
| Some(ty) => ty, |
| None => { |
| tyenv.report_error(pos, "Unknown type for constant"); |
| continue; |
| } |
| }; |
| let name = tyenv.intern_mut(name); |
| tyenv.const_types.insert(name, ty); |
| } |
| } |
| |
| tyenv.return_errors()?; |
| |
| Ok(tyenv) |
| } |
| |
| fn return_errors(&mut self) -> Result<(), Errors> { |
| if self.errors.is_empty() { |
| Ok(()) |
| } else { |
| Err(Errors { |
| errors: std::mem::take(&mut self.errors), |
| filenames: self.filenames.clone(), |
| file_texts: self.file_texts.clone(), |
| }) |
| } |
| } |
| |
| fn type_from_ast(&mut self, tid: TypeId, ty: &ast::Type) -> Option<Type> { |
| let name = self.intern(&ty.name).unwrap(); |
| match &ty.ty { |
| &ast::TypeValue::Primitive(ref id, ..) => { |
| if ty.is_nodebug { |
| self.report_error(ty.pos, "primitive types cannot be marked `nodebug`"); |
| return None; |
| } |
| if ty.is_extern { |
| self.report_error(ty.pos, "primitive types cannot be marked `extern`"); |
| return None; |
| } |
| Some(Type::Primitive(tid, self.intern_mut(id), ty.pos)) |
| } |
| &ast::TypeValue::Enum(ref ty_variants, ..) => { |
| if ty.is_extern && ty.is_nodebug { |
| self.report_error(ty.pos, "external types cannot be marked `nodebug`"); |
| return None; |
| } |
| |
| let mut variants = vec![]; |
| for variant in ty_variants { |
| let combined_ident = |
| ast::Ident(format!("{}.{}", ty.name.0, variant.name.0), variant.name.1); |
| let fullname = self.intern_mut(&combined_ident); |
| let name = self.intern_mut(&variant.name); |
| let id = VariantId(variants.len()); |
| if variants.iter().any(|v: &Variant| v.name == name) { |
| self.report_error( |
| variant.pos, |
| format!("Duplicate variant name in type: '{}'", variant.name.0), |
| ); |
| return None; |
| } |
| let mut fields = vec![]; |
| for field in &variant.fields { |
| let field_name = self.intern_mut(&field.name); |
| if fields.iter().any(|f: &Field| f.name == field_name) { |
| self.report_error( |
| field.pos, |
| format!( |
| "Duplicate field name '{}' in variant '{}' of type", |
| field.name.0, variant.name.0 |
| ), |
| ); |
| return None; |
| } |
| let field_tid = match self.get_type_by_name(&field.ty) { |
| Some(tid) => tid, |
| None => { |
| self.report_error( |
| field.ty.1, |
| format!( |
| "Unknown type '{}' for field '{}' in variant '{}'", |
| field.ty.0, field.name.0, variant.name.0 |
| ), |
| ); |
| return None; |
| } |
| }; |
| fields.push(Field { |
| name: field_name, |
| id: FieldId(fields.len()), |
| ty: field_tid, |
| }); |
| } |
| variants.push(Variant { |
| name, |
| fullname, |
| id, |
| fields, |
| }); |
| } |
| Some(Type::Enum { |
| name, |
| id: tid, |
| is_extern: ty.is_extern, |
| is_nodebug: ty.is_nodebug, |
| variants, |
| pos: ty.pos, |
| }) |
| } |
| } |
| } |
| |
| fn error(&self, pos: Pos, msg: impl Into<String>) -> Error { |
| Error::TypeError { |
| msg: msg.into(), |
| span: Span::new_single(pos), |
| } |
| } |
| |
| fn report_error(&mut self, pos: Pos, msg: impl Into<String>) { |
| let err = self.error(pos, msg); |
| self.errors.push(err); |
| } |
| |
| fn intern_mut(&mut self, ident: &ast::Ident) -> Sym { |
| if let Some(s) = self.sym_map.get(&ident.0).copied() { |
| s |
| } else { |
| let s = Sym(self.syms.len()); |
| self.syms.push(ident.0.clone()); |
| self.sym_map.insert(ident.0.clone(), s); |
| s |
| } |
| } |
| |
| fn intern(&self, ident: &ast::Ident) -> Option<Sym> { |
| self.sym_map.get(&ident.0).copied() |
| } |
| |
| fn get_type_by_name(&self, sym: &ast::Ident) -> Option<TypeId> { |
| self.intern(sym) |
| .and_then(|sym| self.type_map.get(&sym)) |
| .copied() |
| } |
| } |
| |
| #[derive(Clone, Debug, Default)] |
| struct Bindings { |
| /// All bindings accumulated so far within the current rule, including let- |
| /// bindings which have gone out of scope. |
| seen: Vec<BoundVar>, |
| /// Counter for unique scope IDs within this set of bindings. |
| next_scope: usize, |
| /// Stack of the scope IDs for bindings which are currently in scope. |
| in_scope: Vec<usize>, |
| } |
| |
| impl Bindings { |
| fn enter_scope(&mut self) { |
| self.in_scope.push(self.next_scope); |
| self.next_scope += 1; |
| } |
| |
| fn exit_scope(&mut self) { |
| self.in_scope.pop(); |
| } |
| |
| fn add_var(&mut self, name: Sym, ty: TypeId) -> VarId { |
| let id = VarId(self.seen.len()); |
| let var = BoundVar { |
| id, |
| name, |
| ty, |
| scope: *self |
| .in_scope |
| .last() |
| .expect("enter_scope should be called before add_var"), |
| }; |
| log!("binding var {:?}", var); |
| self.seen.push(var); |
| id |
| } |
| |
| fn lookup(&self, name: Sym) -> Option<&BoundVar> { |
| self.seen |
| .iter() |
| .rev() |
| .find(|binding| binding.name == name && self.in_scope.contains(&binding.scope)) |
| } |
| } |
| |
| impl TermEnv { |
| /// Construct the term environment from the AST and the type environment. |
| pub fn from_ast(tyenv: &mut TypeEnv, defs: &ast::Defs) -> Result<TermEnv, Errors> { |
| let mut env = TermEnv { |
| terms: vec![], |
| term_map: StableMap::new(), |
| rules: vec![], |
| converters: StableMap::new(), |
| }; |
| |
| env.collect_pragmas(defs); |
| env.collect_term_sigs(tyenv, defs); |
| env.collect_enum_variant_terms(tyenv); |
| tyenv.return_errors()?; |
| env.collect_constructors(tyenv, defs); |
| env.collect_extractor_templates(tyenv, defs); |
| tyenv.return_errors()?; |
| env.collect_converters(tyenv, defs); |
| tyenv.return_errors()?; |
| env.collect_externs(tyenv, defs); |
| tyenv.return_errors()?; |
| env.collect_rules(tyenv, defs); |
| env.check_for_undefined_decls(tyenv, defs); |
| env.check_for_expr_terms_without_constructors(tyenv, defs); |
| tyenv.return_errors()?; |
| |
| Ok(env) |
| } |
| |
| fn collect_pragmas(&mut self, _: &ast::Defs) { |
| // currently, no pragmas are defined, but the infrastructure is useful to keep around |
| return; |
| } |
| |
| fn collect_term_sigs(&mut self, tyenv: &mut TypeEnv, defs: &ast::Defs) { |
| for def in &defs.defs { |
| match def { |
| &ast::Def::Decl(ref decl) => { |
| let name = tyenv.intern_mut(&decl.term); |
| if let Some(tid) = self.term_map.get(&name) { |
| tyenv.report_error( |
| decl.pos, |
| format!("Duplicate decl for '{}'", decl.term.0), |
| ); |
| tyenv.report_error( |
| self.terms[tid.index()].decl_pos, |
| format!("Duplicate decl for '{}'", decl.term.0), |
| ); |
| } |
| |
| if decl.multi && decl.partial { |
| tyenv.report_error( |
| decl.pos, |
| format!("Term '{}' can't be both multi and partial", decl.term.0), |
| ); |
| } |
| |
| let arg_tys = decl |
| .arg_tys |
| .iter() |
| .map(|id| { |
| tyenv.get_type_by_name(id).ok_or_else(|| { |
| tyenv.report_error(id.1, format!("Unknown arg type: '{}'", id.0)); |
| }) |
| }) |
| .collect::<Result<Vec<_>, _>>(); |
| let arg_tys = match arg_tys { |
| Ok(a) => a, |
| Err(_) => { |
| continue; |
| } |
| }; |
| let ret_ty = match tyenv.get_type_by_name(&decl.ret_ty) { |
| Some(t) => t, |
| None => { |
| tyenv.report_error( |
| decl.ret_ty.1, |
| format!("Unknown return type: '{}'", decl.ret_ty.0), |
| ); |
| continue; |
| } |
| }; |
| |
| let tid = TermId(self.terms.len()); |
| self.term_map.insert(name, tid); |
| let flags = TermFlags { |
| pure: decl.pure, |
| multi: decl.multi, |
| partial: decl.partial, |
| }; |
| self.terms.push(Term { |
| id: tid, |
| decl_pos: decl.pos, |
| name, |
| arg_tys, |
| ret_ty, |
| kind: TermKind::Decl { |
| flags, |
| constructor_kind: None, |
| extractor_kind: None, |
| }, |
| }); |
| } |
| _ => {} |
| } |
| } |
| } |
| |
| fn collect_enum_variant_terms(&mut self, tyenv: &mut TypeEnv) { |
| 'types: for i in 0..tyenv.types.len() { |
| let ty = &tyenv.types[i]; |
| match ty { |
| &Type::Enum { |
| pos, |
| id, |
| ref variants, |
| .. |
| } => { |
| for variant in variants { |
| if self.term_map.contains_key(&variant.fullname) { |
| let variant_name = tyenv.syms[variant.fullname.index()].clone(); |
| tyenv.report_error( |
| pos, |
| format!("Duplicate enum variant constructor: '{}'", variant_name,), |
| ); |
| continue 'types; |
| } |
| let tid = TermId(self.terms.len()); |
| let arg_tys = variant.fields.iter().map(|fld| fld.ty).collect::<Vec<_>>(); |
| let ret_ty = id; |
| self.terms.push(Term { |
| id: tid, |
| decl_pos: pos, |
| name: variant.fullname, |
| arg_tys, |
| ret_ty, |
| kind: TermKind::EnumVariant { |
| variant: variant.id, |
| }, |
| }); |
| self.term_map.insert(variant.fullname, tid); |
| } |
| } |
| _ => {} |
| } |
| } |
| } |
| |
| fn collect_constructors(&mut self, tyenv: &mut TypeEnv, defs: &ast::Defs) { |
| for def in &defs.defs { |
| log!("collect_constructors from def: {:?}", def); |
| match def { |
| &ast::Def::Rule(ref rule) => { |
| let pos = rule.pos; |
| let term = match rule.pattern.root_term() { |
| Some(t) => t, |
| None => { |
| tyenv.report_error( |
| pos, |
| "Rule does not have a term at the LHS root".to_string(), |
| ); |
| continue; |
| } |
| }; |
| let term = match self.get_term_by_name(tyenv, &term) { |
| Some(tid) => tid, |
| None => { |
| tyenv |
| .report_error(pos, "Rule LHS root term is not defined".to_string()); |
| continue; |
| } |
| }; |
| let termdata = &mut self.terms[term.index()]; |
| match &mut termdata.kind { |
| TermKind::Decl { |
| constructor_kind, .. |
| } => { |
| match constructor_kind { |
| None => { |
| *constructor_kind = Some(ConstructorKind::InternalConstructor); |
| } |
| Some(ConstructorKind::InternalConstructor) => { |
| // OK, no error; multiple rules can apply to |
| // one internal constructor term. |
| } |
| Some(ConstructorKind::ExternalConstructor { .. }) => { |
| tyenv.report_error( |
| pos, |
| "Rule LHS root term is incorrect kind; cannot \ |
| be external constructor" |
| .to_string(), |
| ); |
| continue; |
| } |
| } |
| } |
| TermKind::EnumVariant { .. } => { |
| tyenv.report_error( |
| pos, |
| "Rule LHS root term is incorrect kind; cannot be enum variant" |
| .to_string(), |
| ); |
| continue; |
| } |
| } |
| } |
| _ => {} |
| } |
| } |
| } |
| |
| fn collect_extractor_templates(&mut self, tyenv: &mut TypeEnv, defs: &ast::Defs) { |
| let mut extractor_call_graph = BTreeMap::new(); |
| |
| for def in &defs.defs { |
| if let &ast::Def::Extractor(ref ext) = def { |
| let term = match self.get_term_by_name(tyenv, &ext.term) { |
| Some(x) => x, |
| None => { |
| tyenv.report_error( |
| ext.pos, |
| "Extractor macro body definition on a non-existent term".to_string(), |
| ); |
| return; |
| } |
| }; |
| |
| let template = ext.template.make_macro_template(&ext.args[..]); |
| log!("extractor def: {:?} becomes template {:?}", def, template); |
| |
| let mut callees = BTreeSet::new(); |
| template.terms(&mut |pos, t| { |
| if let Some(term) = self.get_term_by_name(tyenv, t) { |
| callees.insert(term); |
| } else { |
| tyenv.report_error( |
| pos, |
| format!( |
| "`{}` extractor definition references unknown term `{}`", |
| ext.term.0, t.0 |
| ), |
| ); |
| } |
| }); |
| extractor_call_graph.insert(term, callees); |
| |
| let termdata = &mut self.terms[term.index()]; |
| match &mut termdata.kind { |
| TermKind::EnumVariant { .. } => { |
| tyenv.report_error( |
| ext.pos, |
| "Extractor macro body defined on term of incorrect kind; cannot be an \ |
| enum variant", |
| ); |
| continue; |
| } |
| TermKind::Decl { |
| flags, |
| extractor_kind, |
| .. |
| } => match extractor_kind { |
| None => { |
| if flags.multi { |
| tyenv.report_error( |
| ext.pos, |
| "A term declared with `multi` cannot have an internal extractor.".to_string()); |
| continue; |
| } |
| *extractor_kind = Some(ExtractorKind::InternalExtractor { template }); |
| } |
| Some(ext_kind) => { |
| tyenv.report_error( |
| ext.pos, |
| "Duplicate extractor definition".to_string(), |
| ); |
| let pos = match ext_kind { |
| ExtractorKind::InternalExtractor { template } => template.pos(), |
| ExtractorKind::ExternalExtractor { pos, .. } => *pos, |
| }; |
| tyenv.report_error( |
| pos, |
| "Extractor was already defined here".to_string(), |
| ); |
| continue; |
| } |
| }, |
| } |
| } |
| } |
| |
| // Check for cycles in the extractor call graph. |
| let mut stack = vec![]; |
| 'outer: for root in extractor_call_graph.keys().copied() { |
| stack.clear(); |
| stack.push((root, vec![root], StableSet::new())); |
| |
| while let Some((caller, path, mut seen)) = stack.pop() { |
| let is_new = seen.insert(caller); |
| if is_new { |
| if let Some(callees) = extractor_call_graph.get(&caller) { |
| stack.extend(callees.iter().map(|callee| { |
| let mut path = path.clone(); |
| path.push(*callee); |
| (*callee, path, seen.clone()) |
| })); |
| } |
| } else { |
| let pos = match &self.terms[caller.index()].kind { |
| TermKind::Decl { |
| extractor_kind: Some(ExtractorKind::InternalExtractor { template }), |
| .. |
| } => template.pos(), |
| _ => { |
| // There must have already been errors recorded. |
| assert!(!tyenv.errors.is_empty()); |
| continue 'outer; |
| } |
| }; |
| |
| let path: Vec<_> = path |
| .iter() |
| .map(|sym| tyenv.syms[sym.index()].as_str()) |
| .collect(); |
| let msg = format!( |
| "`{}` extractor definition is recursive: {}", |
| tyenv.syms[root.index()], |
| path.join(" -> ") |
| ); |
| tyenv.report_error(pos, msg); |
| continue 'outer; |
| } |
| } |
| } |
| } |
| |
| fn collect_converters(&mut self, tyenv: &mut TypeEnv, defs: &ast::Defs) { |
| for def in &defs.defs { |
| match def { |
| &ast::Def::Converter(ast::Converter { |
| ref term, |
| ref inner_ty, |
| ref outer_ty, |
| pos, |
| }) => { |
| let inner_ty_id = match tyenv.get_type_by_name(inner_ty) { |
| Some(ty) => ty, |
| None => { |
| tyenv.report_error( |
| inner_ty.1, |
| format!("Unknown inner type for converter: '{}'", inner_ty.0), |
| ); |
| continue; |
| } |
| }; |
| |
| let outer_ty_id = match tyenv.get_type_by_name(outer_ty) { |
| Some(ty) => ty, |
| None => { |
| tyenv.report_error( |
| outer_ty.1, |
| format!("Unknown outer type for converter: '{}'", outer_ty.0), |
| ); |
| continue; |
| } |
| }; |
| |
| let term_id = match self.get_term_by_name(tyenv, term) { |
| Some(term_id) => term_id, |
| None => { |
| tyenv.report_error( |
| term.1, |
| format!("Unknown term for converter: '{}'", term.0), |
| ); |
| continue; |
| } |
| }; |
| |
| match self.converters.entry((inner_ty_id, outer_ty_id)) { |
| Entry::Vacant(v) => { |
| v.insert(term_id); |
| } |
| Entry::Occupied(_) => { |
| tyenv.report_error( |
| pos, |
| format!( |
| "Converter already exists for this type pair: '{}', '{}'", |
| inner_ty.0, outer_ty.0 |
| ), |
| ); |
| continue; |
| } |
| } |
| } |
| _ => {} |
| } |
| } |
| } |
| |
| fn collect_externs(&mut self, tyenv: &mut TypeEnv, defs: &ast::Defs) { |
| for def in &defs.defs { |
| match def { |
| &ast::Def::Extern(ast::Extern::Constructor { |
| ref term, |
| ref func, |
| pos, |
| }) => { |
| let func_sym = tyenv.intern_mut(func); |
| let term_id = match self.get_term_by_name(tyenv, term) { |
| Some(term) => term, |
| None => { |
| tyenv.report_error( |
| pos, |
| format!("Constructor declared on undefined term '{}'", term.0), |
| ); |
| continue; |
| } |
| }; |
| let termdata = &mut self.terms[term_id.index()]; |
| match &mut termdata.kind { |
| TermKind::Decl { |
| constructor_kind, .. |
| } => match constructor_kind { |
| None => { |
| *constructor_kind = |
| Some(ConstructorKind::ExternalConstructor { name: func_sym }); |
| } |
| Some(ConstructorKind::InternalConstructor) => { |
| tyenv.report_error( |
| pos, |
| format!( |
| "External constructor declared on term that already has rules: {}", |
| term.0, |
| ), |
| ); |
| } |
| Some(ConstructorKind::ExternalConstructor { .. }) => { |
| tyenv.report_error( |
| pos, |
| "Duplicate external constructor definition".to_string(), |
| ); |
| } |
| }, |
| TermKind::EnumVariant { .. } => { |
| tyenv.report_error( |
| pos, |
| format!( |
| "External constructor cannot be defined on enum variant: {}", |
| term.0, |
| ), |
| ); |
| } |
| } |
| } |
| &ast::Def::Extern(ast::Extern::Extractor { |
| ref term, |
| ref func, |
| pos, |
| infallible, |
| }) => { |
| let func_sym = tyenv.intern_mut(func); |
| let term_id = match self.get_term_by_name(tyenv, term) { |
| Some(term) => term, |
| None => { |
| tyenv.report_error( |
| pos, |
| format!("Extractor declared on undefined term '{}'", term.0), |
| ); |
| continue; |
| } |
| }; |
| |
| let termdata = &mut self.terms[term_id.index()]; |
| |
| match &mut termdata.kind { |
| TermKind::Decl { extractor_kind, .. } => match extractor_kind { |
| None => { |
| *extractor_kind = Some(ExtractorKind::ExternalExtractor { |
| name: func_sym, |
| infallible, |
| pos, |
| }); |
| } |
| Some(ExtractorKind::ExternalExtractor { pos: pos2, .. }) => { |
| tyenv.report_error( |
| pos, |
| "Duplicate external extractor definition".to_string(), |
| ); |
| tyenv.report_error( |
| *pos2, |
| "External extractor already defined".to_string(), |
| ); |
| continue; |
| } |
| Some(ExtractorKind::InternalExtractor { template }) => { |
| tyenv.report_error( |
| pos, |
| "Cannot define external extractor for term that already has an \ |
| internal extractor macro body defined" |
| .to_string(), |
| ); |
| tyenv.report_error( |
| template.pos(), |
| "Internal extractor macro body already defined".to_string(), |
| ); |
| continue; |
| } |
| }, |
| TermKind::EnumVariant { .. } => { |
| tyenv.report_error( |
| pos, |
| format!("Cannot define extractor for enum variant '{}'", term.0), |
| ); |
| continue; |
| } |
| } |
| } |
| _ => {} |
| } |
| } |
| } |
| |
| fn collect_rules(&mut self, tyenv: &mut TypeEnv, defs: &ast::Defs) { |
| for def in &defs.defs { |
| match def { |
| &ast::Def::Rule(ref rule) => { |
| let pos = rule.pos; |
| let mut bindings = Bindings::default(); |
| bindings.enter_scope(); |
| |
| let (sym, args) = if let ast::Pattern::Term { sym, args, .. } = &rule.pattern { |
| (sym, args) |
| } else { |
| tyenv.report_error( |
| pos, |
| "Rule does not have a term at the root of its left-hand side" |
| .to_string(), |
| ); |
| continue; |
| }; |
| |
| let root_term = if let Some(term) = self.get_term_by_name(tyenv, sym) { |
| term |
| } else { |
| tyenv.report_error( |
| pos, |
| "Cannot define a rule for an unknown term".to_string(), |
| ); |
| continue; |
| }; |
| |
| let termdata = &self.terms[root_term.index()]; |
| |
| let flags = match &termdata.kind { |
| TermKind::Decl { flags, .. } => flags, |
| _ => { |
| tyenv.report_error( |
| pos, |
| "Cannot define a rule on a left-hand-side that is an enum variant" |
| .to_string(), |
| ); |
| continue; |
| } |
| }; |
| |
| termdata.check_args_count(args, tyenv, pos, sym); |
| let args = self.translate_args(args, termdata, tyenv, &mut bindings); |
| |
| let iflets = rule |
| .iflets |
| .iter() |
| .filter_map(|iflet| { |
| self.translate_iflet(tyenv, iflet, &mut bindings, flags) |
| }) |
| .collect(); |
| let rhs = unwrap_or_continue!(self.translate_expr( |
| tyenv, |
| &rule.expr, |
| Some(termdata.ret_ty), |
| &mut bindings, |
| flags, |
| /* on_lhs */ false, |
| )); |
| |
| bindings.exit_scope(); |
| |
| let prio = if let Some(prio) = rule.prio { |
| if flags.multi { |
| tyenv.report_error( |
| pos, |
| "Cannot set rule priorities in multi-terms".to_string(), |
| ); |
| } |
| prio |
| } else { |
| 0 |
| }; |
| |
| let rid = RuleId(self.rules.len()); |
| self.rules.push(Rule { |
| id: rid, |
| root_term, |
| args, |
| iflets, |
| rhs, |
| vars: bindings.seen, |
| prio, |
| pos, |
| }); |
| } |
| _ => {} |
| } |
| } |
| } |
| |
| fn check_for_undefined_decls(&self, tyenv: &mut TypeEnv, defs: &ast::Defs) { |
| for def in &defs.defs { |
| if let ast::Def::Decl(decl) = def { |
| let term = self.get_term_by_name(tyenv, &decl.term).unwrap(); |
| let term = &self.terms[term.index()]; |
| if !term.has_constructor() && !term.has_extractor() { |
| tyenv.report_error( |
| decl.pos, |
| format!( |
| "no rules, extractor, or external definition for declaration '{}'", |
| decl.term.0 |
| ), |
| ); |
| } |
| } |
| } |
| } |
| |
| fn check_for_expr_terms_without_constructors(&self, tyenv: &mut TypeEnv, defs: &ast::Defs) { |
| for def in &defs.defs { |
| if let ast::Def::Rule(rule) = def { |
| rule.expr.terms(&mut |pos, ident| { |
| let term = match self.get_term_by_name(tyenv, ident) { |
| None => { |
| debug_assert!(!tyenv.errors.is_empty()); |
| return; |
| } |
| Some(t) => t, |
| }; |
| let term = &self.terms[term.index()]; |
| if !term.has_constructor() { |
| tyenv.report_error( |
| pos, |
| format!( |
| "term `{}` cannot be used in an expression because \ |
| it does not have a constructor", |
| ident.0 |
| ), |
| ) |
| } |
| }); |
| } |
| } |
| } |
| |
| fn maybe_implicit_convert_pattern( |
| &self, |
| tyenv: &mut TypeEnv, |
| pattern: &ast::Pattern, |
| inner_ty: TypeId, |
| outer_ty: TypeId, |
| ) -> Option<ast::Pattern> { |
| if let Some(converter_term) = self.converters.get(&(inner_ty, outer_ty)) { |
| if self.terms[converter_term.index()].has_extractor() { |
| // This is a little awkward: we have to |
| // convert back to an Ident, to be |
| // re-resolved. The pos doesn't matter |
| // as it shouldn't result in a lookup |
| // failure. |
| let converter_term_ident = ast::Ident( |
| tyenv.syms[self.terms[converter_term.index()].name.index()].clone(), |
| pattern.pos(), |
| ); |
| let expanded_pattern = ast::Pattern::Term { |
| sym: converter_term_ident, |
| pos: pattern.pos(), |
| args: vec![pattern.clone()], |
| }; |
| |
| return Some(expanded_pattern); |
| } |
| } |
| None |
| } |
| |
| fn translate_pattern( |
| &self, |
| tyenv: &mut TypeEnv, |
| pat: &ast::Pattern, |
| expected_ty: TypeId, |
| bindings: &mut Bindings, |
| ) -> Option<Pattern> { |
| log!("translate_pattern: {:?}", pat); |
| log!("translate_pattern: bindings = {:?}", bindings); |
| match pat { |
| // TODO: flag on primitive type decl indicating it's an integer type? |
| &ast::Pattern::ConstInt { val, pos } => { |
| let ty = &tyenv.types[expected_ty.index()]; |
| if !ty.is_prim() { |
| tyenv.report_error( |
| pos, |
| format!( |
| "expected non-primitive type {}, but found integer literal '{}'", |
| ty.name(tyenv), |
| val, |
| ), |
| ); |
| } |
| Some(Pattern::ConstInt(expected_ty, val)) |
| } |
| &ast::Pattern::ConstPrim { ref val, pos } => { |
| let val = tyenv.intern_mut(val); |
| let const_ty = match tyenv.const_types.get(&val) { |
| Some(ty) => *ty, |
| None => { |
| tyenv.report_error(pos, "Unknown constant"); |
| return None; |
| } |
| }; |
| if expected_ty != const_ty { |
| tyenv.report_error(pos, "Type mismatch for constant"); |
| } |
| Some(Pattern::ConstPrim(const_ty, val)) |
| } |
| &ast::Pattern::Wildcard { .. } => Some(Pattern::Wildcard(expected_ty)), |
| &ast::Pattern::And { ref subpats, .. } => { |
| // If any of the subpatterns fails to type-check, we'll report |
| // an error at that point. Here, just skip it and keep looking |
| // for more errors. |
| let children = subpats |
| .iter() |
| .filter_map(|subpat| { |
| self.translate_pattern(tyenv, subpat, expected_ty, bindings) |
| }) |
| .collect(); |
| Some(Pattern::And(expected_ty, children)) |
| } |
| &ast::Pattern::BindPattern { |
| ref var, |
| ref subpat, |
| pos, |
| } => { |
| let subpat = self.translate_pattern(tyenv, subpat, expected_ty, bindings)?; |
| |
| // The sub-pattern's type should be `expected_ty`. If it isn't, |
| // we've already reported a type error about it, but continue |
| // using the type we actually found in hopes that we'll |
| // generate fewer follow-on error messages. |
| let ty = subpat.ty(); |
| |
| let name = tyenv.intern_mut(var); |
| if bindings.lookup(name).is_some() { |
| tyenv.report_error( |
| pos, |
| format!("Re-bound variable name in LHS pattern: '{}'", var.0), |
| ); |
| // Try to keep going. |
| } |
| let id = bindings.add_var(name, ty); |
| Some(Pattern::BindPattern(ty, id, Box::new(subpat))) |
| } |
| &ast::Pattern::Var { ref var, pos } => { |
| // Look up the variable; if it has already been bound, |
| // then this becomes a `Var` node (which matches the |
| // existing bound value), otherwise it becomes a |
| // `BindPattern` with a wildcard subpattern to capture |
| // at this location. |
| let name = tyenv.intern_mut(var); |
| match bindings.lookup(name) { |
| None => { |
| let id = bindings.add_var(name, expected_ty); |
| Some(Pattern::BindPattern( |
| expected_ty, |
| id, |
| Box::new(Pattern::Wildcard(expected_ty)), |
| )) |
| } |
| Some(bv) => { |
| if expected_ty != bv.ty { |
| tyenv.report_error( |
| pos, |
| format!( |
| "Mismatched types: pattern expects type '{}' but already-bound var '{}' has type '{}'", |
| tyenv.types[expected_ty.index()].name(tyenv), |
| var.0, |
| tyenv.types[bv.ty.index()].name(tyenv), |
| ), |
| ); |
| // Try to keep going for more errors. |
| } |
| Some(Pattern::Var(bv.ty, bv.id)) |
| } |
| } |
| } |
| &ast::Pattern::Term { |
| ref sym, |
| ref args, |
| pos, |
| } => { |
| // Look up the term. |
| let tid = match self.get_term_by_name(tyenv, sym) { |
| Some(t) => t, |
| None => { |
| tyenv.report_error(pos, format!("Unknown term in pattern: '{}'", sym.0)); |
| return None; |
| } |
| }; |
| |
| let termdata = &self.terms[tid.index()]; |
| |
| // Get the return type and arg types. Verify the |
| // expected type of this pattern, if any, against the |
| // return type of the term. Insert an implicit |
| // converter if needed. |
| let ret_ty = termdata.ret_ty; |
| if expected_ty != ret_ty { |
| // Can we do an implicit type conversion? Look |
| // up the converter term, if any. If one has |
| // been registered, and the term has an |
| // extractor, then build an expanded AST node |
| // right here and recurse on it. |
| if let Some(expanded_pattern) = |
| self.maybe_implicit_convert_pattern(tyenv, pat, ret_ty, expected_ty) |
| { |
| return self.translate_pattern( |
| tyenv, |
| &expanded_pattern, |
| expected_ty, |
| bindings, |
| ); |
| } |
| |
| tyenv.report_error( |
| pos, |
| format!( |
| "Mismatched types: pattern expects type '{}' but term has return type '{}'", |
| tyenv.types[expected_ty.index()].name(tyenv), |
| tyenv.types[ret_ty.index()].name(tyenv), |
| ), |
| ); |
| // Try to keep going for more errors. |
| } |
| |
| termdata.check_args_count(args, tyenv, pos, sym); |
| |
| // TODO: check that multi-extractors are only used in terms declared `multi` |
| |
| match &termdata.kind { |
| TermKind::EnumVariant { .. } => {} |
| TermKind::Decl { |
| extractor_kind: Some(ExtractorKind::ExternalExtractor { .. }), |
| .. |
| } => {} |
| TermKind::Decl { |
| extractor_kind: Some(ExtractorKind::InternalExtractor { ref template }), |
| .. |
| } => { |
| // Expand the extractor macro! We create a map |
| // from macro args to AST pattern trees and |
| // then evaluate the template with these |
| // substitutions. |
| log!("internal extractor macro args = {:?}", args); |
| let pat = template.subst_macro_args(&args)?; |
| return self.translate_pattern(tyenv, &pat, expected_ty, bindings); |
| } |
| TermKind::Decl { |
| extractor_kind: None, |
| .. |
| } => { |
| tyenv.report_error( |
| pos, |
| format!( |
| "Cannot use term '{}' that does not have a defined extractor in a \ |
| left-hand side pattern", |
| sym.0 |
| ), |
| ); |
| } |
| } |
| |
| let subpats = self.translate_args(args, termdata, tyenv, bindings); |
| Some(Pattern::Term(ret_ty, tid, subpats)) |
| } |
| &ast::Pattern::MacroArg { .. } => unreachable!(), |
| } |
| } |
| |
| fn translate_args( |
| &self, |
| args: &Vec<ast::Pattern>, |
| termdata: &Term, |
| tyenv: &mut TypeEnv, |
| bindings: &mut Bindings, |
| ) -> Vec<Pattern> { |
| args.iter() |
| .zip(termdata.arg_tys.iter()) |
| .filter_map(|(arg, &arg_ty)| self.translate_pattern(tyenv, arg, arg_ty, bindings)) |
| .collect() |
| } |
| |
| fn maybe_implicit_convert_expr( |
| &self, |
| tyenv: &mut TypeEnv, |
| expr: &ast::Expr, |
| inner_ty: TypeId, |
| outer_ty: TypeId, |
| ) -> Option<ast::Expr> { |
| // Is there a converter for this type mismatch? |
| if let Some(converter_term) = self.converters.get(&(inner_ty, outer_ty)) { |
| if self.terms[converter_term.index()].has_constructor() { |
| let converter_ident = ast::Ident( |
| tyenv.syms[self.terms[converter_term.index()].name.index()].clone(), |
| expr.pos(), |
| ); |
| return Some(ast::Expr::Term { |
| sym: converter_ident, |
| pos: expr.pos(), |
| args: vec![expr.clone()], |
| }); |
| } |
| } |
| None |
| } |
| |
| fn translate_expr( |
| &self, |
| tyenv: &mut TypeEnv, |
| expr: &ast::Expr, |
| ty: Option<TypeId>, |
| bindings: &mut Bindings, |
| root_flags: &TermFlags, |
| on_lhs: bool, |
| ) -> Option<Expr> { |
| log!("translate_expr: {:?}", expr); |
| match expr { |
| &ast::Expr::Term { |
| ref sym, |
| ref args, |
| pos, |
| } => { |
| // Look up the term. |
| let name = tyenv.intern_mut(&sym); |
| let tid = match self.term_map.get(&name) { |
| Some(&t) => t, |
| None => { |
| // Maybe this was actually a variable binding and the user has placed |
| // parens around it by mistake? (See #4775.) |
| if bindings.lookup(name).is_some() { |
| tyenv.report_error( |
| pos, |
| format!( |
| "Unknown term in expression: '{}'. Variable binding under this name exists; try removing the parens?", sym.0)); |
| } else { |
| tyenv.report_error( |
| pos, |
| format!("Unknown term in expression: '{}'", sym.0), |
| ); |
| } |
| return None; |
| } |
| }; |
| let termdata = &self.terms[tid.index()]; |
| |
| // Get the return type and arg types. Verify the |
| // expected type of this pattern, if any, against the |
| // return type of the term, and determine whether we |
| // are doing an implicit conversion. Report an error |
| // if types don't match and no conversion is possible. |
| let ret_ty = termdata.ret_ty; |
| let ty = if ty.is_some() && ret_ty != ty.unwrap() { |
| // Is there a converter for this type mismatch? |
| if let Some(expanded_expr) = |
| self.maybe_implicit_convert_expr(tyenv, expr, ret_ty, ty.unwrap()) |
| { |
| return self.translate_expr( |
| tyenv, |
| &expanded_expr, |
| ty, |
| bindings, |
| root_flags, |
| on_lhs, |
| ); |
| } |
| |
| tyenv.report_error( |
| pos, |
| format!("Mismatched types: expression expects type '{}' but term has return type '{}'", |
| tyenv.types[ty.unwrap().index()].name(tyenv), |
| tyenv.types[ret_ty.index()].name(tyenv))); |
| |
| // Keep going, to discover more errors. |
| ret_ty |
| } else { |
| ret_ty |
| }; |
| |
| if let TermKind::Decl { flags, .. } = &termdata.kind { |
| // On the left-hand side of a rule or in a pure term, only pure terms may be |
| // used. |
| let pure_required = on_lhs || root_flags.pure; |
| if pure_required && !flags.pure { |
| tyenv.report_error( |
| pos, |
| format!( |
| "Used non-pure constructor '{}' in pure expression context", |
| sym.0 |
| ), |
| ); |
| } |
| |
| // Multi-terms may only be used inside other multi-terms. |
| if !root_flags.multi && flags.multi { |
| tyenv.report_error( |
| pos, |
| format!( |
| "Used multi-constructor '{}' but this rule is not in a multi-term", |
| sym.0 |
| ), |
| ); |
| } |
| |
| // Partial terms may always be used on the left-hand side of a rule. On the |
| // right-hand side they may only be used inside other partial terms. |
| let partial_allowed = on_lhs || root_flags.partial; |
| if !partial_allowed && flags.partial { |
| tyenv.report_error( |
| pos, |
| format!( |
| "Rule can't use partial constructor '{}' on RHS; \ |
| try moving it to if-let{}", |
| sym.0, |
| if root_flags.multi { |
| "" |
| } else { |
| " or make this rule's term partial too" |
| } |
| ), |
| ); |
| } |
| } |
| |
| termdata.check_args_count(args, tyenv, pos, sym); |
| |
| // Resolve subexpressions. |
| let subexprs = args |
| .iter() |
| .zip(termdata.arg_tys.iter()) |
| .filter_map(|(arg, &arg_ty)| { |
| self.translate_expr(tyenv, arg, Some(arg_ty), bindings, root_flags, on_lhs) |
| }) |
| .collect(); |
| |
| Some(Expr::Term(ty, tid, subexprs)) |
| } |
| &ast::Expr::Var { ref name, pos } => { |
| let sym = tyenv.intern_mut(name); |
| // Look through bindings, innermost (most recent) first. |
| let bv = match bindings.lookup(sym) { |
| None => { |
| tyenv.report_error(pos, format!("Unknown variable '{}'", name.0)); |
| return None; |
| } |
| Some(bv) => bv, |
| }; |
| |
| // Verify type. Maybe do an implicit conversion. |
| if ty.is_some() && bv.ty != ty.unwrap() { |
| // Is there a converter for this type mismatch? |
| if let Some(expanded_expr) = |
| self.maybe_implicit_convert_expr(tyenv, expr, bv.ty, ty.unwrap()) |
| { |
| return self.translate_expr( |
| tyenv, |
| &expanded_expr, |
| ty, |
| bindings, |
| root_flags, |
| on_lhs, |
| ); |
| } |
| |
| tyenv.report_error( |
| pos, |
| format!( |
| "Variable '{}' has type {} but we need {} in context", |
| name.0, |
| tyenv.types[bv.ty.index()].name(tyenv), |
| tyenv.types[ty.unwrap().index()].name(tyenv) |
| ), |
| ); |
| } |
| |
| Some(Expr::Var(bv.ty, bv.id)) |
| } |
| &ast::Expr::ConstInt { val, pos } => { |
| if ty.is_none() { |
| tyenv.report_error( |
| pos, |
| "integer literal in a context that needs an explicit type".to_string(), |
| ); |
| return None; |
| } |
| let ty = ty.unwrap(); |
| |
| if !tyenv.types[ty.index()].is_prim() { |
| tyenv.report_error( |
| pos, |
| format!( |
| "expected non-primitive type {}, but found integer literal '{}'", |
| tyenv.types[ty.index()].name(tyenv), |
| val, |
| ), |
| ); |
| } |
| Some(Expr::ConstInt(ty, val)) |
| } |
| &ast::Expr::ConstPrim { ref val, pos } => { |
| let val = tyenv.intern_mut(val); |
| let const_ty = match tyenv.const_types.get(&val) { |
| Some(ty) => *ty, |
| None => { |
| tyenv.report_error(pos, "Unknown constant"); |
| return None; |
| } |
| }; |
| if ty.is_some() && const_ty != ty.unwrap() { |
| tyenv.report_error( |
| pos, |
| format!( |
| "Constant '{}' has wrong type: expected {}, but is actually {}", |
| tyenv.syms[val.index()], |
| tyenv.types[ty.unwrap().index()].name(tyenv), |
| tyenv.types[const_ty.index()].name(tyenv) |
| ), |
| ); |
| return None; |
| } |
| Some(Expr::ConstPrim(const_ty, val)) |
| } |
| &ast::Expr::Let { |
| ref defs, |
| ref body, |
| pos, |
| } => { |
| bindings.enter_scope(); |
| |
| // For each new binding... |
| let mut let_defs = vec![]; |
| for def in defs { |
| // Check that the given variable name does not already exist. |
| let name = tyenv.intern_mut(&def.var); |
| |
| // Look up the type. |
| let tid = match tyenv.get_type_by_name(&def.ty) { |
| Some(tid) => tid, |
| None => { |
| tyenv.report_error( |
| pos, |
| format!("Unknown type {} for variable '{}'", def.ty.0, def.var.0), |
| ); |
| continue; |
| } |
| }; |
| |
| // Evaluate the variable's value. |
| let val = Box::new(unwrap_or_continue!(self.translate_expr( |
| tyenv, |
| &def.val, |
| Some(tid), |
| bindings, |
| root_flags, |
| on_lhs, |
| ))); |
| |
| // Bind the var with the given type. |
| let id = bindings.add_var(name, tid); |
| let_defs.push((id, tid, val)); |
| } |
| |
| // Evaluate the body, expecting the type of the overall let-expr. |
| let body = |
| Box::new(self.translate_expr(tyenv, body, ty, bindings, root_flags, on_lhs)?); |
| let body_ty = body.ty(); |
| |
| // Pop the bindings. |
| bindings.exit_scope(); |
| |
| Some(Expr::Let { |
| ty: body_ty, |
| bindings: let_defs, |
| body, |
| }) |
| } |
| } |
| } |
| |
| fn translate_iflet( |
| &self, |
| tyenv: &mut TypeEnv, |
| iflet: &ast::IfLet, |
| bindings: &mut Bindings, |
| root_flags: &TermFlags, |
| ) -> Option<IfLet> { |
| // Translate the expr first. The `if-let` and `if` forms are part of the left-hand side of |
| // the rule. |
| let rhs = self.translate_expr( |
| tyenv, |
| &iflet.expr, |
| None, |
| bindings, |
| root_flags, |
| /* on_lhs */ true, |
| )?; |
| let lhs = self.translate_pattern(tyenv, &iflet.pattern, rhs.ty(), bindings)?; |
| |
| Some(IfLet { lhs, rhs }) |
| } |
| |
| fn get_term_by_name(&self, tyenv: &TypeEnv, sym: &ast::Ident) -> Option<TermId> { |
| tyenv |
| .intern(sym) |
| .and_then(|sym| self.term_map.get(&sym)) |
| .copied() |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| use crate::ast::Ident; |
| use crate::lexer::Lexer; |
| use crate::parser::parse; |
| |
| #[test] |
| fn build_type_env() { |
| let text = r" |
| (type u32 (primitive u32)) |
| (type A extern (enum (B (f1 u32) (f2 u32)) (C (f1 u32)))) |
| "; |
| let ast = parse(Lexer::from_str(text, "file.isle").unwrap()).expect("should parse"); |
| let tyenv = TypeEnv::from_ast(&ast).expect("should not have type-definition errors"); |
| |
| let sym_a = tyenv |
| .intern(&Ident("A".to_string(), Default::default())) |
| .unwrap(); |
| let sym_b = tyenv |
| .intern(&Ident("B".to_string(), Default::default())) |
| .unwrap(); |
| let sym_c = tyenv |
| .intern(&Ident("C".to_string(), Default::default())) |
| .unwrap(); |
| let sym_a_b = tyenv |
| .intern(&Ident("A.B".to_string(), Default::default())) |
| .unwrap(); |
| let sym_a_c = tyenv |
| .intern(&Ident("A.C".to_string(), Default::default())) |
| .unwrap(); |
| let sym_u32 = tyenv |
| .intern(&Ident("u32".to_string(), Default::default())) |
| .unwrap(); |
| let sym_f1 = tyenv |
| .intern(&Ident("f1".to_string(), Default::default())) |
| .unwrap(); |
| let sym_f2 = tyenv |
| .intern(&Ident("f2".to_string(), Default::default())) |
| .unwrap(); |
| |
| assert_eq!(tyenv.type_map.get(&sym_u32).unwrap(), &TypeId(0)); |
| assert_eq!(tyenv.type_map.get(&sym_a).unwrap(), &TypeId(1)); |
| |
| let expected_types = vec![ |
| Type::Primitive( |
| TypeId(0), |
| sym_u32, |
| Pos { |
| file: 0, |
| offset: 19, |
| line: 2, |
| col: 18, |
| }, |
| ), |
| Type::Enum { |
| name: sym_a, |
| id: TypeId(1), |
| is_extern: true, |
| is_nodebug: false, |
| variants: vec![ |
| Variant { |
| name: sym_b, |
| fullname: sym_a_b, |
| id: VariantId(0), |
| fields: vec![ |
| Field { |
| name: sym_f1, |
| id: FieldId(0), |
| ty: TypeId(0), |
| }, |
| Field { |
| name: sym_f2, |
| id: FieldId(1), |
| ty: TypeId(0), |
| }, |
| ], |
| }, |
| Variant { |
| name: sym_c, |
| fullname: sym_a_c, |
| id: VariantId(1), |
| fields: vec![Field { |
| name: sym_f1, |
| id: FieldId(0), |
| ty: TypeId(0), |
| }], |
| }, |
| ], |
| pos: Pos { |
| file: 0, |
| offset: 58, |
| line: 3, |
| col: 18, |
| }, |
| }, |
| ]; |
| |
| assert_eq!(tyenv.types.len(), expected_types.len()); |
| for (i, (actual, expected)) in tyenv.types.iter().zip(&expected_types).enumerate() { |
| assert_eq!(expected, actual, "`{}`th type is not equal!", i); |
| } |
| } |
| } |