From 9774ef9fe888d8b2ec17cbee80ea3341b959a7d7 2021-03-28 16:02:36 From: MH Date: 2021-03-28 16:02:36 Subject: [PATCH] small cleanup pass, added (failing) monomorph test --- diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index a78613e1189f2faa09bf748d2b550dd42158dc54..6825431d551c25f2eab6ab6329d3c1621c5aa011 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -51,21 +51,22 @@ impl std::fmt::Debug for ProtocolDescription { } } impl ProtocolDescription { + // TODO: Allow for multi-file compilation pub fn parse(buffer: &[u8]) -> Result { // TODO: @fixme, keep code compilable, but needs support for multiple // input files. let source = InputSource::from_buffer(buffer).unwrap(); let mut parser = Parser::new(); - parser.feed(source).expect("failed to parse source"); - match parser.parse() { - Ok(root) => { - return Ok(ProtocolDescription { heap: parser.heap, source: parser.modules[0].source.clone(), root }); - } - Err(err) => { - println!("ERROR:\n{}", err); - Err(format!("{}", err)) - } + parser.feed(source).expect("failed to lex source"); + + if let Err(err) = parser.parse() { + println!("ERROR:\n{}", err); + return Err(format!("{}", err)) } + + debug_assert_eq!(parser.modules.len(), 1, "only supporting one module here for now"); + let root = parser.modules[0].root_id; + return Ok(ProtocolDescription { heap: parser.heap, source: parser.modules[0].source.clone(), root }); } pub(crate) fn component_polarities( &self, diff --git a/src/protocol/parser/mod.rs b/src/protocol/parser/mod.rs index 90e97a8ae61e65ce0e5ac6aacca14348c8b3bc62..c2c572555bbb71d197030a636a359dc4f7912b72 100644 --- a/src/protocol/parser/mod.rs +++ b/src/protocol/parser/mod.rs @@ -1,7 +1,6 @@ mod depth_visitor; -mod symbol_table; -// mod type_table_old; -mod type_table; +pub(crate) mod symbol_table; +pub(crate) mod type_table; mod type_resolver; mod visitor; mod visitor_linker; @@ -26,13 +25,15 @@ pub(crate) struct LexedModule { pub(crate) source: InputSource, module_name: Vec, version: Option, - root_id: RootId, + pub(crate) root_id: RootId, } pub struct Parser { pub(crate) heap: Heap, pub(crate) modules: Vec, pub(crate) module_lookup: HashMap, usize>, // from (optional) module name to `modules` idx + pub(crate) symbol_table: SymbolTable, + pub(crate) type_table: TypeTable, } impl Parser { @@ -40,17 +41,12 @@ impl Parser { Parser{ heap: Heap::new(), modules: Vec::new(), - module_lookup: HashMap::new() + module_lookup: HashMap::new(), + symbol_table: SymbolTable::new(), + type_table: TypeTable::new(), } } - // TODO: @fix, temporary implementation to keep code compilable - pub fn new_with_source(source: InputSource) -> Result { - let mut parser = Parser::new(); - parser.feed(source)?; - Ok(parser) - } - pub fn feed(&mut self, mut source: InputSource) -> Result { // Lex the input source let mut lex = Lexer::new(&mut source); @@ -127,23 +123,16 @@ impl Parser { Ok(pd) } - pub fn compile(&mut self) { - // Build module lookup - } - - fn resolve_symbols_and_types(&mut self) -> Result<(SymbolTable, TypeTable), ParseError2> { + fn resolve_symbols_and_types(&mut self) -> Result<(), ParseError2> { // Construct the symbol table to resolve any imports and/or definitions, // then use the symbol table to actually annotate all of the imports. // If the type table is constructed correctly then all imports MUST be // resolvable. - // TODO: Update once namespaced identifiers are implemented - let symbol_table = SymbolTable::new(&self.heap, &self.modules)?; + self.symbol_table.build(&self.heap, &self.modules)?; // Not pretty, but we need to work around rust's borrowing rules, it is // totally safe to mutate the contents of an AST element that we are // not borrowing anywhere else. - // TODO: Maybe directly access heap's members to allow borrowing from - // mutliple members of Heap? Not pretty though... let mut module_index = 0; let mut import_index = 0; loop { @@ -166,19 +155,19 @@ impl Parser { match import { Import::Module(import) => { debug_assert!(import.module_id.is_none(), "module import already resolved"); - let target_module_id = symbol_table.resolve_module(&import.module_name) + let target_module_id = self.symbol_table.resolve_module(&import.module_name) .expect("module import is resolved by symbol table"); import.module_id = Some(target_module_id) }, Import::Symbols(import) => { debug_assert!(import.module_id.is_none(), "module of symbol import already resolved"); - let target_module_id = symbol_table.resolve_module(&import.module_name) + let target_module_id = self.symbol_table.resolve_module(&import.module_name) .expect("symbol import's module is resolved by symbol table"); import.module_id = Some(target_module_id); for symbol in &mut import.symbols { debug_assert!(symbol.definition_id.is_none(), "symbol import already resolved"); - let (_, target_definition_id) = symbol_table.resolve_symbol(module_root_id, &symbol.alias) + let (_, target_definition_id) = self.symbol_table.resolve_symbol(module_root_id, &symbol.alias) .expect("symbol import is resolved by symbol table") .as_definition() .expect("symbol import does not resolve to namespace symbol"); @@ -190,46 +179,64 @@ impl Parser { // All imports in the AST are now annotated. We now use the symbol table // to construct the type table. - let mut type_ctx = TypeCtx::new(&symbol_table, &mut self.heap, &self.modules); - let type_table = TypeTable::new(&mut type_ctx)?; + let mut type_ctx = TypeCtx::new(&self.symbol_table, &mut self.heap, &self.modules); + self.type_table.build_base_types(&mut type_ctx)?; - Ok((symbol_table, type_table)) + Ok(()) } - // TODO: @fix, temporary impl to keep code compilable - pub fn parse(&mut self) -> Result { - assert_eq!(self.modules.len(), 1, "Fix meeeee"); - let root_id = self.modules[0].root_id; - - let (mut symbol_table, mut type_table) = self.resolve_symbols_and_types()?; + pub fn parse(&mut self) -> Result<(), ParseError2> { + self.resolve_symbols_and_types()?; - // TODO: @cleanup - let mut ctx = visitor::Ctx{ - heap: &mut self.heap, - module: &self.modules[0], - symbols: &mut symbol_table, - types: &mut type_table, - }; + // Validate and link all modules let mut visit = ValidityAndLinkerVisitor::new(); - visit.visit_module(&mut ctx)?; - let mut type_visit = TypeResolvingVisitor::new(); + for module in &self.modules { + let mut ctx = visitor::Ctx{ + heap: &mut self.heap, + module, + symbols: &mut self.symbol_table, + types: &mut self.type_table, + }; + visit.visit_module(&mut ctx)?; + } + + // Perform typechecking on all modules + let mut visit = TypeResolvingVisitor::new(); let mut queue = ResolveQueue::new(); - TypeResolvingVisitor::queue_module_definitions(&ctx, &mut queue); + for module in &self.modules { + let ctx = visitor::Ctx{ + heap: &mut self.heap, + module, + symbols: &mut self.symbol_table, + types: &mut self.type_table, + }; + TypeResolvingVisitor::queue_module_definitions(&ctx, &mut queue); + }; while !queue.is_empty() { let top = queue.pop().unwrap(); - println!("Resolving root={}, def={}, mono={:?}", top.root_id.index, top.definition_id.index, top.monomorph_types); - type_visit.handle_module_definition(&mut ctx, &mut queue, top)?; + let mut ctx = visitor::Ctx{ + heap: &mut self.heap, + module: &self.modules[top.root_id.index as usize], + symbols: &mut self.symbol_table, + types: &mut self.type_table, + }; + visit.handle_module_definition(&mut ctx, &mut queue, top)?; } - if let Err((position, message)) = Self::parse_inner(&mut self.heap, root_id) { - return Err(ParseError2::new_error(&self.modules[0].source, position, &message)) + // Perform remaining steps + // TODO: Phase out at some point + for module in &self.modules { + let root_id = module.root_id; + if let Err((position, message)) = Self::parse_inner(&mut self.heap, root_id) { + return Err(ParseError2::new_error(&self.modules[0].source, position, &message)) + } } // let mut writer = ASTWriter::new(); // let mut file = std::fs::File::create(std::path::Path::new("ast.txt")).unwrap(); // writer.write_ast(&mut file, &self.heap); - Ok(root_id) + Ok(()) } pub fn parse_inner(h: &mut Heap, pd: RootId) -> VisitorResult { @@ -252,143 +259,4 @@ impl Parser { Ok(()) } -} - -#[cfg(test)] -mod tests { - use std::fs::File; - use std::io::Read; - use std::path::Path; - - use super::*; - - // #[test] - fn positive_tests() { - for resource in TestFileIter::new("testdata/parser/positive", "pdl") { - let resource = resource.expect("read testdata filepath"); - // println!(" * running: {}", &resource); - let path = Path::new(&resource); - let source = InputSource::from_file(&path).unwrap(); - // println!("DEBUG -- input:\n{}", String::from_utf8_lossy(&source.input)); - let mut parser = Parser::new_with_source(source).expect("parse source"); - match parser.parse() { - Ok(_) => {} - Err(err) => { - println!(" > file: {}", &resource); - println!("{}", err); - assert!(false); - } - } - } - } - - // #[test] - fn negative_tests() { - for resource in TestFileIter::new("testdata/parser/negative", "pdl") { - let resource = resource.expect("read testdata filepath"); - let path = Path::new(&resource); - let expect = path.with_extension("txt"); - let mut source = InputSource::from_file(&path).unwrap(); - let mut parser = Parser::new_with_source(source).expect("construct parser"); - match parser.parse() { - Ok(pd) => { - println!("Expected parse error:"); - - let mut cev: Vec = Vec::new(); - let mut f = File::open(expect).unwrap(); - f.read_to_end(&mut cev).unwrap(); - println!("{}", String::from_utf8_lossy(&cev)); - assert!(false); - } - Err(err) => { - let expected = format!("{}", err); - println!("{}", &expected); - - let mut cev: Vec = Vec::new(); - let mut f = File::open(expect).unwrap(); - f.read_to_end(&mut cev).unwrap(); - println!("{}", String::from_utf8_lossy(&cev)); - - assert_eq!(expected.as_bytes(), cev); - } - } - } - } - - // #[test] - fn counterexample_tests() { - for resource in TestFileIter::new("testdata/parser/counterexamples", "pdl") { - let resource = resource.expect("read testdata filepath"); - let path = Path::new(&resource); - let source = InputSource::from_file(&path).unwrap(); - let mut parser = Parser::new_with_source(source).expect("construct parser"); - - fn print_header(s: &str) { - println!("{}", "=".repeat(80)); - println!(" > File: {}", s); - println!("{}", "=".repeat(80)); - } - - match parser.parse() { - Ok(parsed) => { - print_header(&resource); - println!("\n SUCCESS\n\n --- source:\n{}", String::from_utf8_lossy(&parser.modules[0].source.input)); - }, - Err(err) => { - print_header(&resource); - println!( - "\n FAILURE\n\n --- error:\n{}\n --- source:\n{}", - err, - String::from_utf8_lossy(&parser.modules[0].source.input) - ) - } - } - } - } - - struct TestFileIter { - iter: std::fs::ReadDir, - root: String, - extension: String - } - - impl TestFileIter { - fn new(root_dir: &str, extension: &str) -> Self { - let path = Path::new(root_dir); - assert!(path.is_dir(), "root '{}' is not a directory", root_dir); - - let iter = std::fs::read_dir(path).expect("list dir contents"); - - Self { - iter, - root: root_dir.to_string(), - extension: extension.to_string(), - } - } - } - - impl Iterator for TestFileIter { - type Item = Result; - - fn next(&mut self) -> Option { - while let Some(entry) = self.iter.next() { - if let Err(e) = entry { - return Some(Err(format!("failed to read dir entry, because: {}", e))); - } - let entry = entry.unwrap(); - - let path = entry.path(); - if !path.is_file() { continue; } - - let extension = path.extension(); - if extension.is_none() { continue; } - let extension = extension.unwrap().to_string_lossy(); - if extension != self.extension { continue; } - - return Some(Ok(path.to_string_lossy().to_string())); - } - - None - } - } -} +} \ No newline at end of file diff --git a/src/protocol/parser/symbol_table.rs b/src/protocol/parser/symbol_table.rs index ed684488fbc1a30862e45d0ad78ec5753e8ecc46..0a9c6b3e2c8069226cb549db8c4f2ca3e41ee67d 100644 --- a/src/protocol/parser/symbol_table.rs +++ b/src/protocol/parser/symbol_table.rs @@ -75,8 +75,14 @@ pub(crate) struct SymbolTable { } impl SymbolTable { - pub(crate) fn new(heap: &Heap, modules: &[LexedModule]) -> Result { - // Sanity check + pub(crate) fn new() -> Self { + Self{ module_lookup: HashMap::new(), symbol_lookup: HashMap::new() } + } + + pub(crate) fn build(&mut self, heap: &Heap, modules: &[LexedModule]) -> Result<(), ParseError2> { + // Sanity checks + debug_assert!(self.module_lookup.is_empty()); + debug_assert!(self.symbol_lookup.is_empty()); if cfg!(debug_assertions) { for (index, module) in modules.iter().enumerate() { debug_assert_eq!( @@ -88,11 +94,11 @@ impl SymbolTable { // Preparation: create a lookup from module name to root id. This does // not take aliasing into account. - let mut module_lookup = HashMap::with_capacity(modules.len()); + self.module_lookup.reserve(modules.len()); for module in modules { // TODO: Maybe put duplicate module name checking here? // TODO: @string - module_lookup.insert(module.module_name.clone(), module.root_id); + self.module_lookup.insert(module.module_name.clone(), module.root_id); } // Preparation: determine total number of imports we will be inserting @@ -107,7 +113,7 @@ impl SymbolTable { Import::Symbols(import) => { if import.symbols.is_empty() { // Add all symbols from the other module - match module_lookup.get(&import.module_name) { + match self.module_lookup.get(&import.module_name) { Some(target_module_id) => { lookup_reserve_size += heap[*target_module_id].definitions.len() }, @@ -127,10 +133,7 @@ impl SymbolTable { lookup_reserve_size += module_root.definitions.len(); } - let mut table = Self{ - module_lookup, - symbol_lookup: HashMap::with_capacity(lookup_reserve_size) - }; + self.symbol_lookup.reserve(lookup_reserve_size); // First pass: we go through all of the modules and add lookups to // symbols that are defined within that module. Cross-module imports are @@ -140,7 +143,7 @@ impl SymbolTable { for definition_id in &root.definitions { let definition = &heap[*definition_id]; let identifier = definition.identifier(); - if let Err(previous_position) = table.add_definition_symbol( + if let Err(previous_position) = self.add_definition_symbol( module.root_id, identifier.position, &identifier.value, module.root_id, *definition_id ) { @@ -161,7 +164,7 @@ impl SymbolTable { match import { Import::Module(import) => { // Find the module using its name - let target_root_id = table.resolve_module(&import.module_name); + let target_root_id = self.resolve_module(&import.module_name); if target_root_id.is_none() { return Err(ParseError2::new_error(&module.source, import.position, "Could not resolve module")); } @@ -171,7 +174,7 @@ impl SymbolTable { } // Add the target module under its alias - if let Err(previous_position) = table.add_namespace_symbol( + if let Err(previous_position) = self.add_namespace_symbol( module.root_id, import.position, &import.alias, target_root_id ) { @@ -183,7 +186,7 @@ impl SymbolTable { }, Import::Symbols(import) => { // Find the target module using its name - let target_root_id = table.resolve_module(&import.module_name); + let target_root_id = self.resolve_module(&import.module_name); if target_root_id.is_none() { return Err(ParseError2::new_error(&module.source, import.position, "Could not resolve module of symbol imports")); } @@ -198,7 +201,7 @@ impl SymbolTable { for definition_id in &heap[target_root_id].definitions { let definition = &heap[*definition_id]; let identifier = definition.identifier(); - if let Err(previous_position) = table.add_definition_symbol( + if let Err(previous_position) = self.add_definition_symbol( module.root_id, import.position, &identifier.value, target_root_id, *definition_id ) { @@ -229,7 +232,7 @@ impl SymbolTable { // to "import a module's imported symbol". And so if we do find // a symbol match, we need to make sure it is a definition from // within that module by checking `source_root_id == target_root_id` - let target_symbol = table.resolve_symbol(target_root_id, &symbol.name); + let target_symbol = self.resolve_symbol(target_root_id, &symbol.name); let symbol_definition_id = match target_symbol { Some(target_symbol) => { match target_symbol.symbol { @@ -258,7 +261,7 @@ impl SymbolTable { } let symbol_definition_id = symbol_definition_id.unwrap(); - if let Err(previous_position) = table.add_definition_symbol( + if let Err(previous_position) = self.add_definition_symbol( module.root_id, symbol.position, &symbol.alias, target_root_id, symbol_definition_id ) { @@ -288,10 +291,10 @@ impl SymbolTable { } debug_assert_eq!( - table.symbol_lookup.len(), lookup_reserve_size, + self.symbol_lookup.len(), lookup_reserve_size, "miscalculated reserved size for symbol lookup table" ); - Ok(table) + Ok(()) } /// Resolves a module by its defined name diff --git a/src/protocol/parser/type_table.rs b/src/protocol/parser/type_table.rs index 23fac82a8ef1b7bed1943c401650645b1cfbfe37..c0c177905a1b940b1a16a69b8e5651f6482523d9 100644 --- a/src/protocol/parser/type_table.rs +++ b/src/protocol/parser/type_table.rs @@ -309,10 +309,21 @@ impl<'a> TypeCtx<'a> { } impl TypeTable { - /// Construct a new type table without any resolved types. Types will be - /// resolved on-demand. - pub(crate) fn new(ctx: &mut TypeCtx) -> Result { + /// Construct a new type table without any resolved types. + pub(crate) fn new() -> Self { + Self{ + lookup: HashMap::new(), + iter: TypeIterator::new(), + parser_type_iter: VecDeque::with_capacity(64), + } + } + + pub(crate) fn build_base_types(&mut self, ctx: &mut TypeCtx) -> Result<(), ParseError2> { // Make sure we're allowed to cast root_id to index into ctx.modules + debug_assert!(self.lookup.is_empty()); + debug_assert!(self.iter.top().is_none()); + debug_assert!(self.parser_type_iter.is_empty()); + if cfg!(debug_assertions) { for (index, module) in ctx.modules.iter().enumerate() { debug_assert_eq!(index, module.root_id.index as usize); @@ -321,24 +332,20 @@ impl TypeTable { // Use context to guess hashmap size let reserve_size = ctx.heap.definitions.len(); - let mut table = Self{ - lookup: HashMap::with_capacity(reserve_size), - iter: TypeIterator::new(), - parser_type_iter: VecDeque::with_capacity(64), - }; + self.lookup.reserve(reserve_size); // TODO: @cleanup Rework this hack for root_idx in 0..ctx.modules.len() { let last_definition_idx = ctx.heap[ctx.modules[root_idx].root_id].definitions.len(); for definition_idx in 0..last_definition_idx { let definition_id = ctx.heap[ctx.modules[root_idx].root_id].definitions[definition_idx]; - table.resolve_base_definition(ctx, definition_id)?; + self.resolve_base_definition(ctx, definition_id)?; } } - debug_assert_eq!(table.lookup.len(), reserve_size, "mismatch in reserved size of type table"); + debug_assert_eq!(self.lookup.len(), reserve_size, "mismatch in reserved size of type table"); - Ok(table) + Ok(()) } /// Retrieves base definition from type table. We must be able to retrieve diff --git a/src/protocol/parser/type_table_old.rs b/src/protocol/parser/type_table_old.rs deleted file mode 100644 index 7785670279273b406be984c3421516d1793d3a5a..0000000000000000000000000000000000000000 --- a/src/protocol/parser/type_table_old.rs +++ /dev/null @@ -1,697 +0,0 @@ -// TODO: @fix PrimitiveType for enums/unions -use crate::protocol::ast::*; -use crate::protocol::parser::symbol_table::{SymbolTable, Symbol}; -use crate::protocol::inputsource::*; -use crate::protocol::parser::*; - -use std::collections::HashMap; -use crate::common::Formatter; - -#[derive(Copy, Clone, PartialEq, Eq)] -pub enum TypeClass { - Enum, - Union, - Struct, - Function, - Component -} - -impl TypeClass { - pub(crate) fn display_name(&self) -> &'static str { - match self { - TypeClass::Enum => "enum", - TypeClass::Union => "enum", - TypeClass::Struct => "struct", - TypeClass::Function => "function", - TypeClass::Component => "component", - } - } -} - -impl std::fmt::Display for TypeClass { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.display_name()) - } -} - -pub enum DefinedType { - Enum(EnumType), - Union(UnionType), - Struct(StructType), - Function(FunctionType), - Component(ComponentType) -} - -impl DefinedType { - pub(crate) fn type_class(&self) -> TypeClass { - match self { - DefinedType::Enum(_) => TypeClass::Enum, - DefinedType::Union(_) => TypeClass::Union, - DefinedType::Struct(_) => TypeClass::Struct, - DefinedType::Function(_) => TypeClass::Function, - DefinedType::Component(_) => TypeClass::Component - } - } -} - -// TODO: Also support maximum u64 value -pub struct EnumVariant { - identifier: Identifier, - value: i64, -} - -/// `EnumType` is the classical C/C++ enum type. It has various variants with -/// an assigned integer value. The integer values may be user-defined, -/// compiler-defined, or a mix of the two. If a user assigns the same enum -/// value multiple times, we assume the user is an expert and we consider both -/// variants to be equal to one another. -pub struct EnumType { - variants: Vec, - representation: PrimitiveType, -} - -pub struct UnionVariant { - identifier: Identifier, - embedded_type: Option, - tag_value: i64, -} - -/// `UnionType` is the algebraic datatype (or sum type, or discriminated union). -/// A value is an element of the union, identified by its tag, and may contain -/// a single subtype. -pub struct UnionType { - variants: Vec, - tag_representation: PrimitiveType -} - -pub struct StructField { - identifier: Identifier, - field_type: TypeAnnotationId, -} - -pub struct StructType { - fields: Vec, -} - -pub struct FunctionArgument { - identifier: Identifier, - argument_type: TypeAnnotationId, -} - -pub struct FunctionType { - return_type: TypeAnnotationId, - arguments: Vec -} - -pub struct ComponentType { - variant: ComponentVariant, - arguments: Vec -} - -pub struct TypeTable { - lookup: HashMap, -} - -enum LookupResult { - BuiltIn, - Resolved((RootId, DefinitionId)), - Unresolved((RootId, DefinitionId)), - Error((InputPosition, String)), -} - -/// `TypeTable` is responsible for walking the entire lexed AST and laying out -/// the various user-defined types in terms of bytes and offsets. This process -/// may be pseudo-recursive (meaning: it is implemented in a recursive fashion, -/// but not in the recursive-function-call kind of way) in case a type depends -/// on another type to be resolved. -/// TODO: Distinction between resolved types and unresolved types (regarding the -/// mixed use of BuiltIns and resolved types) is a bit yucky at the moment, -/// will have to come up with something nice in the future. -/// TODO: Need to factor out the repeated lookup in some kind of state machine. -impl TypeTable { - pub(crate) fn new( - symbols: &SymbolTable, heap: &Heap, modules: &[LexedModule] - ) -> Result { - if cfg!(debug_assertions) { - for (index, module) in modules.iter().enumerate() { - debug_assert_eq!(index, module.root_id.index as usize) - } - } - - // Estimate total number of definitions we will encounter - let num_definitions = heap.definitions.len(); - let mut table = TypeTable{ - lookup: HashMap::with_capacity(num_definitions), - }; - - // Perform the breadcrumb-based type parsing. For now we do not allow - // cyclic types. - // TODO: Allow cyclic types. However, we want to have an implementation - // that is somewhat efficient: if a type is cyclic, then we have to - // insert a pointer somewhere. However, we don't want to insert them - // everywhere, for each possible type. Without any further context the - // decision to place a pointer somewhere is "random", we need to know - // how the type is used to have an informed opinion on where to place - // the pointer. - enum Breadcrumb { - Linear((usize, usize)), - Jumping((RootId, DefinitionId)) - } - - // Helper to handle the return value from lookup_type_definition. If - // resolved/builtin we return `true`, if an error we complete the error - // and return it. If unresolved then we check for cyclic types and - // return an error if cyclic, otherwise return false (indicating we need - // to continue in the breadcrumb-based resolving loop) - let handle_lookup_result = | - heap: &Heap, all_modules: &[LexedModule], cur_module: &LexedModule, - breadcrumbs: &mut Vec, result: LookupResult - | -> Result { - return match result { - LookupResult::BuiltIn | LookupResult::Resolved(_) => Ok(true), - LookupResult::Unresolved((new_root_id, new_definition_id)) => { - // Check for cyclic dependencies - let mut is_cyclic = false; - for breadcrumb in breadcrumbs.iter() { - let (root_id, definition_id) = match breadcrumb { - Breadcrumb::Linear((root_index, definition_index)) => { - let root_id = all_modules[*root_index].root_id; - let definition_id = heap[root_id].definitions[*definition_index]; - (root_id, definition_id) - }, - Breadcrumb::Jumping(root_id_and_definition_id) => *root_id_and_definition_id - }; - - if root_id == new_root_id && definition_id == new_definition_id { - // Oh noes! - is_cyclic = true; - break; - } - } - - if is_cyclic { - let mut error = ParseError2::new_error( - &all_modules[new_root_id.index as usize].source, - heap[new_definition_id].position(), - "Evaluating this definition results in a a cyclic dependency" - ); - for (index, breadcrumb) in breadcrumbs.iter().enumerate() { - match breadcrumb { - Breadcrumb::Linear((root_index, definition_index)) => { - debug_assert_eq!(index, 0); - error = error.with_postfixed_info( - &all_modules[*root_index].source, - heap[heap[all_modules[*root_index].root_id].definitions[*definition_index]].position(), - "The cycle started with this definition" - ) - }, - Breadcrumb::Jumping((root_id, definition_id)) => { - debug_assert!(index > 0); - error = error.with_postfixed_info( - &all_modules[root_id.index as usize].source, - heap[*definition_id].position(), - "Which depends on this definition" - ) - } - } - } - Err(error) - } else { - breadcrumbs.push(Breadcrumb::Jumping((new_root_id, new_definition_id))); - Ok(false) - } - }, - LookupResult::Error((position, message)) => { - Err(ParseError2::new_error(&cur_module.source, position, &message)) - } - } - }; - - let mut module_index = 0; - let mut definition_index = 0; - let mut breadcrumbs = Vec::with_capacity(32); // if a user exceeds this, the user sucks at programming - while module_index < modules.len() { - // Go to next module if needed - { - let root = &heap[modules[module_index].root_id]; - if definition_index >= root.definitions.len() { - module_index += 1; - definition_index = 0; - continue; - } - } - - // Construct breadcrumbs in case we need to follow some types around - debug_assert!(breadcrumbs.is_empty()); - breadcrumbs.push(Breadcrumb::Linear((module_index, definition_index))); - 'resolve_loop: while !breadcrumbs.is_empty() { - // Retrieve module, the module's root and the definition - let (module, definition_id) = match breadcrumbs.last().unwrap() { - Breadcrumb::Linear((module_index, definition_index)) => { - let module = &modules[*module_index]; - let root = &heap[module.root_id]; - let definition_id = root.definitions[*definition_index]; - (module, definition_id) - }, - Breadcrumb::Jumping((root_id, definition_id)) => { - let module = &modules[root_id.index as usize]; - debug_assert_eq!(module.root_id, *root_id); - (module, *definition_id) - } - }; - - let definition = &heap[definition_id]; - - // Because we might have chased around to this particular - // definition before, we check if we haven't resolved the type - // already. - if table.lookup.contains_key(&definition_id) { - breadcrumbs.pop(); - continue; - } - - match definition { - Definition::Enum(definition) => { - // Check the definition to see if we're dealing with an - // enum or a union. If we find any union variants then - // we immediately check if the type is already resolved. - assert!(definition.poly_vars.is_empty(), "Polyvars for enum not yet implemented"); - let mut has_tag_values = None; - let mut has_int_values = None; - for variant in &definition.variants { - match &variant.value { - EnumVariantValue::None => {}, - EnumVariantValue::Integer(_) => { - if has_int_values.is_none() { has_int_values = Some(variant.position); } - }, - EnumVariantValue::Type(variant_type) => { - if has_tag_values.is_none() { has_tag_values = Some(variant.position); } - - let variant_type = &heap[*variant_type]; - - let lookup_result = lookup_type_definition( - heap, &table, symbols, module.root_id, - &variant_type.the_type.primitive - ); - if !handle_lookup_result(heap, modules, module, &mut breadcrumbs, lookup_result)? { - continue 'resolve_loop; - } - }, - } - } - - if has_tag_values.is_some() && has_int_values.is_some() { - // Not entirely illegal, but probably not desired - let tag_pos = has_tag_values.unwrap(); - let int_pos = has_int_values.unwrap(); - return Err( - ParseError2::new_error(&module.source, definition.position, "Illegal combination of enum integer variant(s) and enum union variant(s)") - .with_postfixed_info(&module.source, int_pos, "Explicitly assigning an integer value here") - .with_postfixed_info(&module.source, tag_pos, "Explicitly declaring a union variant here") - ) - } - - // If here, then the definition is a valid discriminated - // union with all of its types resolved, or a valid - // enum. - - // Decide whether to implement as enum or as union - let is_union = has_tag_values.is_some(); - if is_union { - // Implement as discriminated union. Because we - // checked the availability of types above, we are - // safe to lookup type definitions - let mut tag_value = -1; - let mut variants = Vec::with_capacity(definition.variants.len()); - for variant in &definition.variants { - tag_value += 1; - let embedded_type = match &variant.value { - EnumVariantValue::None => { - None - }, - EnumVariantValue::Type(type_annotation_id) => { - // Type should be resolvable, we checked this above - let type_annotation = &heap[*type_annotation_id]; - // TODO: Remove the assert once I'm clear on how to layout "the types" of types - if cfg!(debug_assertions) { - ensure_type_definition(heap, &table, symbols, module.root_id, &type_annotation.the_type.primitive); - } - - Some(*type_annotation_id) - }, - EnumVariantValue::Integer(_) => { - debug_assert!(false, "Encountered `Integer` variant after asserting enum is a discriminated union"); - unreachable!(); - } - }; - - variants.push(UnionVariant{ - identifier: variant.identifier.clone(), - embedded_type, - tag_value, - }) - } - - table.add_definition(definition_id, DefinedType::Union(UnionType{ - variants, - tag_representation: enum_representation(tag_value) - })); - } else { - // Implement as regular enum - let mut enum_value = -1; // TODO: allow u64 max size - let mut variants = Vec::with_capacity(definition.variants.len()); - for variant in &definition.variants { - enum_value += 1; - match &variant.value { - EnumVariantValue::None => { - variants.push(EnumVariant{ - identifier: variant.identifier.clone(), - value: enum_value, - }); - }, - EnumVariantValue::Integer(override_value) => { - enum_value = *override_value; - variants.push(EnumVariant{ - identifier: variant.identifier.clone(), - value: enum_value, - }); - }, - EnumVariantValue::Type(_) => { - debug_assert!(false, "Encountered `Type` variant after asserting enum is not a discriminated union"); - unreachable!(); - } - } - } - - table.add_definition(definition_id, DefinedType::Enum(EnumType{ - variants, - representation: enum_representation(enum_value), - })); - } - }, - Definition::Struct(definition) => { - // Before we start allocating fields, make sure we can - // actually resolve all of the field types - assert!(definition.poly_vars.is_empty(), "Polyvars for struct not yet implemented"); - for field_definition in &definition.fields { - let type_definition = &heap[field_definition.the_type]; - let lookup_result = lookup_type_definition( - heap, &table, symbols, module.root_id, - &type_definition.the_type.primitive - ); - if !handle_lookup_result(heap, modules, module, &mut breadcrumbs, lookup_result)? { - continue 'resolve_loop; - } - } - - // We can resolve everything - let mut fields = Vec::with_capacity(definition.fields.len()); - for field_definition in &definition.fields { - let type_annotation = &heap[field_definition.the_type]; - if cfg!(debug_assertions) { - ensure_type_definition(heap, &table, symbols, module.root_id, &type_annotation.the_type.primitive); - } - - fields.push(StructField{ - identifier: field_definition.field.clone(), - field_type: field_definition.the_type - }); - } - - table.add_definition(definition_id, DefinedType::Struct(StructType{ - fields, - })); - }, - Definition::Component(definition) => { - // As always, ensure all parameter types are resolved - assert!(definition.poly_vars.is_empty(), "Polyvars for component not yet implemented"); - for parameter_id in &definition.parameters { - let parameter = &heap[*parameter_id]; - let type_definition = &heap[parameter.type_annotation]; - let lookup_result = lookup_type_definition( - heap, &table, symbols, module.root_id, - &type_definition.the_type.primitive - ); - if !handle_lookup_result(heap, modules, module, &mut breadcrumbs, lookup_result)? { - continue 'resolve_loop; - } - } - - // We can resolve everything - let mut parameters = Vec::with_capacity(definition.parameters.len()); - for parameter_id in &definition.parameters { - let parameter = &heap[*parameter_id]; - let type_definition = &heap[parameter.type_annotation]; - if cfg!(debug_assertions) { - ensure_type_definition(heap, &table, symbols, module.root_id, &type_definition.the_type.primitive); - } - - parameters.push(FunctionArgument{ - identifier: parameter.identifier.clone(), - argument_type: parameter.type_annotation, - }); - } - - table.add_definition(definition_id, DefinedType::Component(ComponentType{ - variant: definition.variant, - arguments: parameters, // Arguments, parameters, tomayto, tomahto - })); - }, - Definition::Function(definition) => { - assert!(definition.poly_vars.is_empty(), "Polyvars for function not yet implemented"); - // Handle checking the return type - let lookup_result = lookup_type_definition( - heap, &table, symbols, module.root_id, - &heap[definition.return_type].the_type.primitive - ); - if !handle_lookup_result(heap, modules, module, &mut breadcrumbs, lookup_result)? { - continue 'resolve_loop; - } - - for parameter_id in &definition.parameters { - let parameter = &heap[*parameter_id]; - let type_definition = &heap[parameter.type_annotation]; - let lookup_result = lookup_type_definition( - heap, &table, symbols, module.root_id, - &type_definition.the_type.primitive - ); - if !handle_lookup_result(heap, modules, module, &mut breadcrumbs, lookup_result)? { - continue 'resolve_loop; - } - } - - // Resolve function's types - let mut parameters = Vec::with_capacity(definition.parameters.len()); - for parameter_id in &definition.parameters { - let parameter = &heap[*parameter_id]; - let type_definition = &heap[parameter.type_annotation]; - if cfg!(debug_assertions) { - ensure_type_definition(heap, &table, symbols, module.root_id, &type_definition.the_type.primitive); - } - - parameters.push(FunctionArgument{ - identifier: parameter.identifier.clone(), - argument_type: parameter.type_annotation - }); - } - - table.add_definition(definition_id, DefinedType::Function(FunctionType{ - return_type: definition.return_type.clone(), - arguments: parameters, - })); - }, - } - - // If here, then we layed out the current type definition under - // investigation, so: - debug_assert!(!breadcrumbs.is_empty()); - breadcrumbs.pop(); - } - - // Go to next definition - definition_index += 1; - } - - debug_assert_eq!( - num_definitions, table.lookup.len(), - "expected {} (reserved) definitions in table, got {}", - num_definitions, table.lookup.len() - ); - - Ok(table) - } - - pub(crate) fn get_definition(&self, definition_id: &DefinitionId) -> Option<&DefinedType> { - self.lookup.get(definition_id) - } - - fn add_definition(&mut self, definition_id: DefinitionId, definition: DefinedType) { - debug_assert!(!self.lookup.contains_key(&definition_id), "already added definition"); - self.lookup.insert(definition_id, definition); - } -} - -/// Attempts to lookup a type definition using a namespaced identifier. We have -/// three success cases: the first is simply that the type is a `BuiltIn` type. -/// In the two other cases both we find the definition of the type in the symbol -/// table, but in one case it is already `Resolved` in the type table, and in -/// the other case it is `Unresolved`. In this last case the type has to be -/// resolved before we're able to use it in the `TypeTable` construction -/// algorithm. -/// In the `Error` case something goes wrong with resolving the type. The error -/// message aims to be as helpful as possible to the user. -/// The caller should ensure that the `module_root` is where the `identifier` -/// lives. -fn lookup_type_definition( - heap: &Heap, types: &TypeTable, symbols: &SymbolTable, - module_root: RootId, type_to_resolve: &PrimitiveType -) -> LookupResult { - if let PrimitiveType::Symbolic(type_to_resolve) = type_to_resolve { - let identifier = &type_to_resolve.identifier; - - match symbols.resolve_namespaced_symbol(module_root, identifier) { - None => { - // Failed to find anything at all - LookupResult::Error((identifier.position, String::from("Unknown type"))) - }, - Some((symbol, mut identifier_iter)) => { - match symbol.symbol { - Symbol::Namespace(_root_id) => { - // Reference to a namespace, which is not a type. However, - // the error message depends on whether we have identifiers - // remaining or not - if identifier_iter.num_remaining() == 0 { - LookupResult::Error(( - identifier.position, - String::from("Expected a type, got a module name") - )) - } else { - let next_identifier = identifier_iter.next().unwrap(); - LookupResult::Error(( - identifier.position, - format!("Cannot find symbol '{}' in this module", String::from_utf8_lossy(next_identifier)) - )) - } - }, - Symbol::Definition((definition_root_id, definition_id)) => { - // Got a definition, but we may also have more identifier's - // remaining in the identifier iterator - let definition = &heap[definition_id]; - if identifier_iter.num_remaining() == 0 { - // See if the type is resolved, and make sure it is - // a non-function, non-component type. Ofcourse - // these are valid types, but we cannot (yet?) use - // them as function arguments, struct fields or enum - // variants - match definition { - Definition::Component(definition) => { - return LookupResult::Error(( - identifier.position, - format!( - "Cannot use the component '{}' as an embedded type", - String::from_utf8_lossy(&definition.identifier.value) - ) - )); - }, - Definition::Function(definition) => { - return LookupResult::Error(( - identifier.position, - format!( - "Cannot use the function '{}' as an embedded type", - String::from_utf8_lossy(&definition.identifier.value) - ) - )); - }, - Definition::Enum(_) | Definition::Struct(_) => {} - } - - return if types.lookup.contains_key(&definition_id) { - LookupResult::Resolved((definition_root_id, definition_id)) - } else { - LookupResult::Unresolved((definition_root_id, definition_id)) - } - } else if identifier_iter.num_remaining() == 1 { - // This is always invalid, but if the type is an - // enumeration or a union then we want to return a - // different error message. - if definition.is_enum() { - let last_identifier = identifier_iter.next().unwrap(); - return LookupResult::Error(( - identifier.position, - format!( - "Expected a type, but got a (possible) enum variant '{}'. Only the enum '{}' itself can be used as a type", - String::from_utf8_lossy(last_identifier), - String::from_utf8_lossy(&definition.identifier().value) - ) - )); - } - } - - // Too much identifiers (>1) for an enumeration, or not an - // enumeration and we had more identifiers remaining - LookupResult::Error(( - identifier.position, - format!( - "Unknown type '{}', did you mean to use '{}'?", - String::from_utf8_lossy(&identifier.value), - String::from_utf8_lossy(&definition.identifier().value) - ) - )) - } - } - } - } - } else { - LookupResult::BuiltIn - } -} - -/// Debugging function to ensure a type is resolved (calling -/// `lookup_type_definition` and ensuring its return value is `BuiltIn` or -/// `Resolved` -#[cfg(debug_assertions)] -fn ensure_type_definition( - heap: &Heap, types: &TypeTable, symbols: &SymbolTable, - module_root: RootId, type_to_resolve: &PrimitiveType -) { - match lookup_type_definition(heap, types, symbols, module_root, type_to_resolve) { - LookupResult::BuiltIn | LookupResult::Resolved(_) => {}, - LookupResult::Unresolved((_, definition_id)) => { - assert!( - false, - "Expected that type definition for {} was resolved by the type table, but it wasn't", - String::from_utf8_lossy(&heap[definition_id].identifier().value) - ) - }, - LookupResult::Error((_, error)) => { - let message = if let PrimitiveType::Symbolic(symbolic) = type_to_resolve { - format!( - "Expected that type definition for {} was resolved by the type table, but it returned: {}", - String::from_utf8_lossy(&symbolic.identifier.value), &error - ) - } else { - format!( - "Expected (non-symbolic!?) type definition to be resolved, but it returned: {}", - &error - ) - }; - assert!(false, "{}", message) - } - } -} - -/// Determines an enumeration's integer representation type, or a union's tag -/// type, using the maximum value of the tag. The returned type is always a -/// builtin type. -/// TODO: Fix for maximum u64 value -fn enum_representation(max_tag_value: i64) -> PrimitiveType { - if max_tag_value <= u8::max_value() as i64 { - PrimitiveType::Byte - } else if max_tag_value <= u16::max_value() as i64 { - PrimitiveType::Short - } else if max_tag_value <= u32::max_value() as i64 { - PrimitiveType::Int - } else { - PrimitiveType::Long - } -} \ No newline at end of file diff --git a/src/protocol/parser/visitor_linker.rs b/src/protocol/parser/visitor_linker.rs index d1c4f942276b7b00e798d058d955b18e6e48f071..9e25aca30a4dd55fef959f8625b3d29c6af509f7 100644 --- a/src/protocol/parser/visitor_linker.rs +++ b/src/protocol/parser/visitor_linker.rs @@ -14,7 +14,6 @@ use super::visitor::{ Visitor2, VisitorResult }; -use std::hint::unreachable_unchecked; #[derive(PartialEq, Eq)] enum DefinitionType { diff --git a/src/protocol/tests/mod.rs b/src/protocol/tests/mod.rs index 016c773671e0a092fa43042f13ba6e8b94f3b48f..2c42b52e7b51356450f51f2d62d9a505be7aaf47 100644 --- a/src/protocol/tests/mod.rs +++ b/src/protocol/tests/mod.rs @@ -2,5 +2,6 @@ mod utils; mod lexer; mod parser_validation; mod parser_inference; +mod parser_monomorphs; pub(crate) use utils::{Tester}; \ No newline at end of file diff --git a/src/protocol/tests/parser_monomorphs.rs b/src/protocol/tests/parser_monomorphs.rs new file mode 100644 index 0000000000000000000000000000000000000000..e3b889c6dc00bd544cb5a0ce6619b210364a81a7 --- /dev/null +++ b/src/protocol/tests/parser_monomorphs.rs @@ -0,0 +1,38 @@ +/// parser_monomorphs.rs +/// +/// Simple tests to make sure that all of the appropriate monomorphs are +/// instantiated + +use super::*; + +#[test] +fn test_struct_monomorphs() { + Tester::new_single_source_expect_ok( + "no polymorph", + "struct Integer{ int field }" + ).for_struct("Integer", |s| { s + .assert_num_monomorphs(0); + }); + + Tester::new_single_source_expect_ok( + "single polymorph", + " + struct Number{ T number } + int instantiator() { + auto a = Number{ number: 0 }; + auto b = Number{ number: 1 }; + auto c = Number{ number: 2 }; + auto d = Number{ number: 3 }; + auto e = Number>{ number: Number{ number: 4 }}; + return 0; + } + " + ).for_struct("Number", |s| { s + .assert_has_monomorph("byte") + .assert_has_monomorph("short") + .assert_has_monomorph("int") + .assert_has_monomorph("long") + .assert_has_monomorph("Number") + .assert_num_monomorphs(5); + }); +} \ No newline at end of file diff --git a/src/protocol/tests/utils.rs b/src/protocol/tests/utils.rs index 0cc0c0a36cccf4e4cda17159e0317da0004e9ddc..209c50695e626572d8575659c5804a7db511f15a 100644 --- a/src/protocol/tests/utils.rs +++ b/src/protocol/tests/utils.rs @@ -1,6 +1,23 @@ -use crate::protocol::ast::*; -use crate::protocol::inputsource::*; -use crate::protocol::parser::*; +use crate::protocol::{ + ast::*, + inputsource::*, + parser::{ + *, + type_table::TypeTable, + symbol_table::SymbolTable, + }, +}; + +// Carries information about the test into utility structures for builder-like +// assertions +#[derive(Clone, Copy)] +struct TestCtx<'a> { + test_name: &'a str, + heap: &'a Heap, + modules: &'a Vec, + types: &'a TypeTable, + symbols: &'a SymbolTable, +} //------------------------------------------------------------------------------ // Interface for parsing and compiling @@ -55,7 +72,6 @@ impl Tester { } } - parser.compile(); if let Err(err) = parser.parse() { return AstTesterResult::Err(AstErrTester::new(self.test_name, err)) } @@ -105,6 +121,8 @@ pub(crate) struct AstOkTester { test_name: String, modules: Vec, heap: Heap, + symbols: SymbolTable, + types: TypeTable, } impl AstOkTester { @@ -112,7 +130,9 @@ impl AstOkTester { Self { test_name, modules: parser.modules, - heap: parser.heap + heap: parser.heap, + symbols: parser.symbol_table, + types: parser.type_table, } } @@ -125,7 +145,7 @@ impl AstOkTester { } // Found struct with the same name - let tester = StructTester::new(&self.test_name, definition, &self.heap); + let tester = StructTester::new(self.ctx(), definition); f(tester); found = true; break @@ -150,7 +170,7 @@ impl AstOkTester { } // Found function - let tester = FunctionTester::new(&self.test_name, definition, &self.heap); + let tester = FunctionTester::new(self.ctx(), definition); f(tester); found = true; break; @@ -165,6 +185,16 @@ impl AstOkTester { ); unreachable!(); } + + fn ctx(&self) -> TestCtx { + TestCtx{ + test_name: &self.test_name, + modules: &self.modules, + heap: &self.heap, + types: &self.types, + symbols: &self.symbols, + } + } } //------------------------------------------------------------------------------ @@ -172,21 +202,66 @@ impl AstOkTester { //------------------------------------------------------------------------------ pub(crate) struct StructTester<'a> { - test_name: &'a str, + ctx: TestCtx<'a>, def: &'a StructDefinition, - heap: &'a Heap, } impl<'a> StructTester<'a> { - fn new(test_name: &'a str, def: &'a StructDefinition, heap: &'a Heap) -> Self { - Self{ test_name, def, heap } + fn new(ctx: TestCtx<'a>, def: &'a StructDefinition) -> Self { + Self{ ctx, def } } pub(crate) fn assert_num_fields(self, num: usize) -> Self { assert_eq!( num, self.def.fields.len(), "[{}] Expected {} struct fields, but found {} for {}", - self.test_name, num, self.def.fields.len(), self.assert_postfix() + self.ctx.test_name, num, self.def.fields.len(), self.assert_postfix() + ); + self + } + + pub(crate) fn assert_num_monomorphs(self, num: usize) -> Self { + let type_def = self.ctx.types.get_base_definition(&self.def.this.upcast()).unwrap(); + assert_eq!( + num, type_def.monomorphs.len(), + "[{}] Expected {} monomorphs, but found {} for {}", + self.ctx.test_name, num, type_def.monomorphs.len(), self.assert_postfix() + ); + self + } + + /// Asserts that a monomorph exist, separate polymorphic variable types by + /// a semicolon. + pub(crate) fn assert_has_monomorph(self, serialized_monomorph: &str) -> Self { + let definition_id = self.def.this.upcast(); + let type_def = self.ctx.types.get_base_definition(&definition_id).unwrap(); + + let mut full_buffer = String::new(); + full_buffer.push('['); + for (monomorph_idx, monomorph) in type_def.monomorphs.iter().enumerate() { + let mut buffer = String::new(); + for (element_idx, monomorph_element) in monomorph.iter().enumerate() { + if element_idx != 0 { buffer.push(';'); } + serialize_concrete_type(&mut buffer, self.ctx.heap, definition_id, monomorph_element); + } + + if buffer == serialized_monomorph { + // Found an exact match + return self + } + + if monomorph_idx != 0 { + full_buffer.push_str(", "); + } + full_buffer.push('"'); + full_buffer.push_str(&buffer); + full_buffer.push('"'); + } + full_buffer.push(']'); + + assert!( + false, "[{}] Expected to find monomorph {}, but got {} for {}", + self.ctx.test_name, serialized_monomorph, &full_buffer, self.assert_postfix() ); self } @@ -195,7 +270,7 @@ impl<'a> StructTester<'a> { // Find field with specified name for field in &self.def.fields { if String::from_utf8_lossy(&field.field.value) == name { - let tester = StructFieldTester::new(self.test_name, field, self.heap); + let tester = StructFieldTester::new(self.ctx, field); f(tester); return self; } @@ -203,7 +278,7 @@ impl<'a> StructTester<'a> { assert!( false, "[{}] Could not find struct field '{}' for {}", - self.test_name, name, self.assert_postfix() + self.ctx.test_name, name, self.assert_postfix() ); unreachable!(); } @@ -223,30 +298,29 @@ impl<'a> StructTester<'a> { } pub(crate) struct StructFieldTester<'a> { - test_name: &'a str, + ctx: TestCtx<'a>, def: &'a StructFieldDefinition, - heap: &'a Heap, } impl<'a> StructFieldTester<'a> { - fn new(test_name: &'a str, def: &'a StructFieldDefinition, heap: &'a Heap) -> Self { - Self{ test_name, def, heap } + fn new(ctx: TestCtx<'a>, def: &'a StructFieldDefinition) -> Self { + Self{ ctx, def } } pub(crate) fn assert_parser_type(self, expected: &str) -> Self { let mut serialized_type = String::new(); - serialize_parser_type(&mut serialized_type, &self.heap, self.def.parser_type); + serialize_parser_type(&mut serialized_type, &self.ctx.heap, self.def.parser_type); assert_eq!( expected, &serialized_type, "[{}] Expected type '{}', but got '{}' for {}", - self.test_name, expected, &serialized_type, self.assert_postfix() + self.ctx.test_name, expected, &serialized_type, self.assert_postfix() ); self } fn assert_postfix(&self) -> String { let mut serialized_type = String::new(); - serialize_parser_type(&mut serialized_type, &self.heap, self.def.parser_type); + serialize_parser_type(&mut serialized_type, &self.ctx.heap, self.def.parser_type); format!( "StructField{{ name: {}, parser_type: {} }}", String::from_utf8_lossy(&self.def.field.value), serialized_type @@ -255,24 +329,23 @@ impl<'a> StructFieldTester<'a> { } pub(crate) struct FunctionTester<'a> { - test_name: &'a str, + ctx: TestCtx<'a>, def: &'a Function, - heap: &'a Heap, } impl<'a> FunctionTester<'a> { - fn new(test_name: &'a str, def: &'a Function, heap: &'a Heap) -> Self { - Self{ test_name, def, heap } + fn new(ctx: TestCtx<'a>, def: &'a Function) -> Self { + Self{ ctx, def } } pub(crate) fn for_variable(self, name: &str, f: F) -> Self { // Find the memory statement in order to find the local let mem_stmt_id = seek_stmt( - self.heap, self.def.body, + self.ctx.heap, self.def.body, &|stmt| { if let Statement::Local(local) = stmt { if let LocalStatement::Memory(memory) = local { - let local = &self.heap[memory.variable]; + let local = &self.ctx.heap[memory.variable]; if local.identifier.value == name.as_bytes() { return true; } @@ -285,19 +358,19 @@ impl<'a> FunctionTester<'a> { assert!( mem_stmt_id.is_some(), "[{}] Failed to find variable '{}' in {}", - self.test_name, name, self.assert_postfix() + self.ctx.test_name, name, self.assert_postfix() ); let mem_stmt_id = mem_stmt_id.unwrap(); - let local_id = self.heap[mem_stmt_id].as_memory().variable; - let local = &self.heap[local_id]; + let local_id = self.ctx.heap[mem_stmt_id].as_memory().variable; + let local = &self.ctx.heap[local_id]; // Find the assignment expression that follows it let assignment_id = seek_expr_in_stmt( - self.heap, self.def.body, + self.ctx.heap, self.def.body, &|expr| { if let Expression::Assignment(assign_expr) = expr { - if let Expression::Variable(variable_expr) = &self.heap[assign_expr.left] { + if let Expression::Variable(variable_expr) = &self.ctx.heap[assign_expr.left] { if variable_expr.position.offset == local.identifier.position.offset { return true; } @@ -310,15 +383,15 @@ impl<'a> FunctionTester<'a> { assert!( assignment_id.is_some(), "[{}] Failed to find assignment to variable '{}' in {}", - self.test_name, name, self.assert_postfix() + self.ctx.test_name, name, self.assert_postfix() ); - let assignment = &self.heap[assignment_id.unwrap()]; + let assignment = &self.ctx.heap[assignment_id.unwrap()]; // Construct tester and pass to tester function let tester = VariableTester::new( - self.test_name, self.def.this.upcast(), local, - assignment.as_assignment(), self.heap + self.ctx, self.def.this.upcast(), local, + assignment.as_assignment() ); f(tester); @@ -335,28 +408,27 @@ impl<'a> FunctionTester<'a> { pub(crate) struct VariableTester<'a> { - test_name: &'a str, + ctx: TestCtx<'a>, definition_id: DefinitionId, local: &'a Local, assignment: &'a AssignmentExpression, - heap: &'a Heap, } impl<'a> VariableTester<'a> { fn new( - test_name: &'a str, definition_id: DefinitionId, local: &'a Local, assignment: &'a AssignmentExpression, heap: &'a Heap + ctx: TestCtx<'a>, definition_id: DefinitionId, local: &'a Local, assignment: &'a AssignmentExpression ) -> Self { - Self{ test_name, definition_id, local, assignment, heap } + Self{ ctx, definition_id, local, assignment } } pub(crate) fn assert_parser_type(self, expected: &str) -> Self { let mut serialized = String::new(); - serialize_parser_type(&mut serialized, self.heap, self.local.parser_type); + serialize_parser_type(&mut serialized, self.ctx.heap, self.local.parser_type); assert_eq!( expected, &serialized, "[{}] Expected parser type '{}', but got '{}' for {}", - self.test_name, expected, &serialized, self.assert_postfix() + self.ctx.test_name, expected, &serialized, self.assert_postfix() ); self } @@ -364,14 +436,14 @@ impl<'a> VariableTester<'a> { pub(crate) fn assert_concrete_type(self, expected: &str) -> Self { let mut serialized = String::new(); serialize_concrete_type( - &mut serialized, self.heap, self.definition_id, + &mut serialized, self.ctx.heap, self.definition_id, &self.assignment.concrete_type ); assert_eq!( expected, &serialized, "[{}] Expected concrete type '{}', but got '{}' for {}", - self.test_name, expected, &serialized, self.assert_postfix() + self.ctx.test_name, expected, &serialized, self.assert_postfix() ); self }