use crate::protocol::ast::*; use crate::protocol::inputsource::*; use crate::protocol::parser::*; //------------------------------------------------------------------------------ // Interface for parsing and compiling //------------------------------------------------------------------------------ pub(crate) struct Tester { test_name: String, sources: Vec } impl Tester { /// Constructs a new tester, allows adding multiple sources before compiling pub(crate) fn new(test_name: S) -> Self { Self{ test_name: test_name.to_string(), sources: Vec::new() } } /// Utility for quick tests that use a single source file and expect the /// compilation to succeed. pub(crate) fn new_single_source_expect_ok(test_name: T, source: S) -> AstOkTester { Self::new(test_name) .with_source(source) .compile() .expect_ok() } /// Utility for quick tests that use a single source file and expect the /// compilation to fail. pub(crate) fn new_single_source_expect_err(test_name: T, source: S) -> AstErrTester { Self::new(test_name) .with_source(source) .compile() .expect_err() } pub(crate) fn with_source(mut self, source: S) -> Self { self.sources.push(source.to_string()); self } pub(crate) fn compile(self) -> AstTesterResult { let mut parser = Parser::new(); for (source_idx, source) in self.sources.into_iter().enumerate() { let mut cursor = std::io::Cursor::new(source); let input_source = InputSource::new("", &mut cursor) .expect(&format!("parsing source {}", source_idx + 1)); if let Err(err) = parser.feed(input_source) { return AstTesterResult::Err(AstErrTester::new(self.test_name, err)) } } parser.compile(); if let Err(err) = parser.parse() { return AstTesterResult::Err(AstErrTester::new(self.test_name, err)) } AstTesterResult::Ok(AstOkTester::new(self.test_name, parser)) } } pub(crate) enum AstTesterResult { Ok(AstOkTester), Err(AstErrTester) } impl AstTesterResult { pub(crate) fn expect_ok(self) -> AstOkTester { match self { AstTesterResult::Ok(v) => v, AstTesterResult::Err(err) => { let wrapped = ErrorTester{ test_name: &err.test_name, error: &err.error }; println!("DEBUG: Full error:\n{}", &err.error); assert!( false, "[{}] Expected compilation to succeed, but it failed with {}", err.test_name, wrapped.assert_postfix() ); unreachable!(); } } } pub(crate) fn expect_err(self) -> AstErrTester { match self { AstTesterResult::Ok(ok) => { assert!(false, "[{}] Expected compilation to fail, but it succeeded", ok.test_name); unreachable!(); }, AstTesterResult::Err(err) => err, } } } //------------------------------------------------------------------------------ // Interface for successful compilation //------------------------------------------------------------------------------ pub(crate) struct AstOkTester { test_name: String, modules: Vec, heap: Heap, } impl AstOkTester { fn new(test_name: String, parser: Parser) -> Self { Self { test_name, modules: parser.modules, heap: parser.heap } } pub(crate) fn for_struct(self, name: &str, f: F) -> Self { let mut found = false; for definition in self.heap.definitions.iter() { if let Definition::Struct(definition) = definition { if String::from_utf8_lossy(&definition.identifier.value) != name { continue; } // Found struct with the same name let tester = StructTester::new(&self.test_name, definition, &self.heap); f(tester); found = true; break } } if found { return self } assert!( false, "[{}] Failed to find definition for struct '{}'", self.test_name, name ); unreachable!() } pub(crate) fn for_function(self, name: &str, f: F) -> Self { let mut found = false; for definition in self.heap.definitions.iter() { if let Definition::Function(definition) = definition { if String::from_utf8_lossy(&definition.identifier.value) != name { continue; } // Found function let tester = FunctionTester::new(&self.test_name, definition, &self.heap); f(tester); found = true; break; } } if found { return self } assert!( false, "[{}] failed to find definition for function '{}'", self.test_name, name ); unreachable!(); } } //------------------------------------------------------------------------------ // Utilities for successful compilation //------------------------------------------------------------------------------ pub(crate) struct StructTester<'a> { test_name: &'a str, 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 } } 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 } pub(crate) fn for_field(self, name: &str, f: F) -> Self { // 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); f(tester); return self; } } assert!( false, "[{}] Could not find struct field '{}' for {}", self.test_name, name, self.assert_postfix() ); unreachable!(); } fn assert_postfix(&self) -> String { let mut v = String::new(); v.push_str("Struct{ name: "); v.push_str(&String::from_utf8_lossy(&self.def.identifier.value)); v.push_str(", fields: ["); for (field_idx, field) in self.def.fields.iter().enumerate() { if field_idx != 0 { v.push_str(", "); } v.push_str(&String::from_utf8_lossy(&field.field.value)); } v.push_str("] }"); v } } pub(crate) struct StructFieldTester<'a> { test_name: &'a str, 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 } } 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); assert_eq!( expected, &serialized_type, "[{}] Expected type '{}', but got '{}' for {}", self.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); format!( "StructField{{ name: {}, parser_type: {} }}", String::from_utf8_lossy(&self.def.field.value), serialized_type ) } } pub(crate) struct FunctionTester<'a> { test_name: &'a str, 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 } } 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, &|stmt| { if let Statement::Local(local) = stmt { if let LocalStatement::Memory(memory) = local { let local = &self.heap[memory.variable]; if local.identifier.value == name.as_bytes() { return true; } } } false } ); assert!( mem_stmt_id.is_some(), "[{}] Failed to find variable '{}' in {}", self.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]; // Find the assignment expression that follows it let assignment_id = seek_expr_in_stmt( self.heap, self.def.body, &|expr| { if let Expression::Assignment(assign_expr) = expr { if let Expression::Variable(variable_expr) = &self.heap[assign_expr.left] { if variable_expr.position.offset == local.identifier.position.offset { return true; } } } false } ); assert!( assignment_id.is_some(), "[{}] Failed to find assignment to variable '{}' in {}", self.test_name, name, self.assert_postfix() ); let assignment = &self.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 ); f(tester); self } fn assert_postfix(&self) -> String { format!( "Function{{ name: {} }}", &String::from_utf8_lossy(&self.def.identifier.value) ) } } pub(crate) struct VariableTester<'a> { test_name: &'a str, 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 ) -> Self { Self{ test_name, definition_id, local, assignment, heap } } 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); assert_eq!( expected, &serialized, "[{}] Expected parser type '{}', but got '{}' for {}", self.test_name, expected, &serialized, self.assert_postfix() ); self } 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, &self.assignment.concrete_type ); assert_eq!( expected, &serialized, "[{}] Expected concrete type '{}', but got '{}' for {}", self.test_name, expected, &serialized, self.assert_postfix() ); self } fn assert_postfix(&self) -> String { format!( "Variable{{ name: {} }}", &String::from_utf8_lossy(&self.local.identifier.value) ) } } //------------------------------------------------------------------------------ // Interface for failed compilation //------------------------------------------------------------------------------ pub(crate) struct AstErrTester { test_name: String, error: ParseError2, } impl AstErrTester { fn new(test_name: String, error: ParseError2) -> Self { Self{ test_name, error } } pub(crate) fn error(&self, f: F) { // Maybe multiple errors will be supported in the future let tester = ErrorTester{ test_name: &self.test_name, error: &self.error }; f(tester) } } //------------------------------------------------------------------------------ // Utilities for failed compilation //------------------------------------------------------------------------------ pub(crate) struct ErrorTester<'a> { test_name: &'a str, error: &'a ParseError2, } impl<'a> ErrorTester<'a> { pub(crate) fn assert_num(self, num: usize) -> Self { assert_eq!( num, self.error.statements.len(), "[{}] expected error to consist of '{}' parts, but encountered '{}' for {}", self.test_name, num, self.error.statements.len(), self.assert_postfix() ); self } pub(crate) fn assert_ctx_has(self, idx: usize, msg: &str) -> Self { assert!( self.error.statements[idx].context.contains(msg), "[{}] expected error statement {}'s context to contain '{}' for {}", self.test_name, idx, msg, self.assert_postfix() ); self } pub(crate) fn assert_msg_has(self, idx: usize, msg: &str) -> Self { assert!( self.error.statements[idx].message.contains(msg), "[{}] expected error statement {}'s message to contain '{}' for {}", self.test_name, idx, msg, self.assert_postfix() ); self } /// Seeks the index of the pattern in the context message, then checks if /// the input position corresponds to that index. pub (crate) fn assert_occurs_at(self, idx: usize, pattern: &str) -> Self { let pos = self.error.statements[idx].context.find(pattern); assert!( pos.is_some(), "[{}] incorrect occurs_at: '{}' could not be found in the context for {}", self.test_name, pattern, self.assert_postfix() ); let pos = pos.unwrap(); let col = self.error.statements[idx].position.col(); assert_eq!( pos + 1, col, "[{}] Expected error to occur at column {}, but found it at {} for {}", self.test_name, pos + 1, col, self.assert_postfix() ); self } fn assert_postfix(&self) -> String { let mut v = String::new(); v.push_str("error: ["); for (idx, stmt) in self.error.statements.iter().enumerate() { if idx != 0 { v.push_str(", "); } v.push_str(&format!("{{ context: {}, message: {} }}", &stmt.context, stmt.message)); } v.push(']'); v } } //------------------------------------------------------------------------------ // Generic utilities //------------------------------------------------------------------------------ fn serialize_parser_type(buffer: &mut String, heap: &Heap, id: ParserTypeId) { use ParserTypeVariant as PTV; let p = &heap[id]; match &p.variant { PTV::Message => buffer.push_str("msg"), PTV::Bool => buffer.push_str("bool"), PTV::Byte => buffer.push_str("byte"), PTV::Short => buffer.push_str("short"), PTV::Int => buffer.push_str("int"), PTV::Long => buffer.push_str("long"), PTV::String => buffer.push_str("string"), PTV::IntegerLiteral => buffer.push_str("intlit"), PTV::Inferred => buffer.push_str("auto"), PTV::Array(sub_id) => { serialize_parser_type(buffer, heap, *sub_id); buffer.push_str("[]"); }, PTV::Input(sub_id) => { buffer.push_str("in<"); serialize_parser_type(buffer, heap, *sub_id); buffer.push('>'); }, PTV::Output(sub_id) => { buffer.push_str("out<"); serialize_parser_type(buffer, heap, *sub_id); buffer.push('>'); }, PTV::Symbolic(symbolic) => { buffer.push_str(&String::from_utf8_lossy(&symbolic.identifier.value)); if symbolic.poly_args.len() > 0 { buffer.push('<'); for (poly_idx, poly_arg) in symbolic.poly_args.iter().enumerate() { if poly_idx != 0 { buffer.push(','); } serialize_parser_type(buffer, heap, *poly_arg); } buffer.push('>'); } } } } fn serialize_concrete_type(buffer: &mut String, heap: &Heap, def: DefinitionId, concrete: &ConcreteType) { // Retrieve polymorphic variables, if present (since we're dealing with a // concrete type we only expect procedure types) let poly_vars = match &heap[def] { Definition::Function(func) => &func.poly_vars, Definition::Component(comp) => &comp.poly_vars, _ => unreachable!("Error in testing utility: did not expect non-procedure type for concrete type serialization"), }; fn serialize_recursive( buffer: &mut String, heap: &Heap, poly_vars: &Vec, concrete: &ConcreteType, mut idx: usize ) -> usize { use ConcreteTypePart as CTP; let part = &concrete.parts[idx]; match part { CTP::Marker(poly_idx) => { buffer.push_str(&String::from_utf8_lossy(&poly_vars[*poly_idx].value)); }, CTP::Void => buffer.push_str("void"), CTP::Message => buffer.push_str("msg"), CTP::Bool => buffer.push_str("bool"), CTP::Byte => buffer.push_str("byte"), CTP::Short => buffer.push_str("short"), CTP::Int => buffer.push_str("int"), CTP::Long => buffer.push_str("long"), CTP::String => buffer.push_str("string"), CTP::Array => { idx = serialize_recursive(buffer, heap, poly_vars, concrete, idx + 1); buffer.push_str("[]"); idx += 1; }, CTP::Slice => { idx = serialize_recursive(buffer, heap, poly_vars, concrete, idx + 1); buffer.push_str("[..]"); idx += 1; }, CTP::Input => { buffer.push_str("in<"); idx = serialize_recursive(buffer, heap, poly_vars, concrete, idx + 1); buffer.push('>'); idx += 1; }, CTP::Output => { buffer.push_str("out<"); idx = serialize_recursive(buffer, heap, poly_vars, concrete, idx + 1); buffer.push('>'); idx += 1 }, CTP::Instance(definition_id, num_sub) => { let definition_name = heap[*definition_id].identifier(); buffer.push_str(&String::from_utf8_lossy(&definition_name.value)); buffer.push('<'); for sub_idx in 0..*num_sub { if sub_idx != 0 { buffer.push(','); } idx = serialize_recursive(buffer, heap, poly_vars, concrete, idx + 1); } buffer.push('>'); idx += 1; } } idx } serialize_recursive(buffer, heap, poly_vars, concrete, 0); } fn seek_stmt bool>(heap: &Heap, start: StatementId, f: &F) -> Option { let stmt = &heap[start]; if f(stmt) { return Some(start); } // This statement wasn't it, try to recurse let matched = match stmt { Statement::Block(block) => { for sub_id in &block.statements { if let Some(id) = seek_stmt(heap, *sub_id, f) { return Some(id); } } None }, Statement::Labeled(stmt) => seek_stmt(heap, stmt.body, f), Statement::If(stmt) => { if let Some(id) = seek_stmt(heap,stmt.true_body, f) { return Some(id); } else if let Some(id) = seek_stmt(heap, stmt.false_body, f) { return Some(id); } None }, Statement::While(stmt) => seek_stmt(heap, stmt.body, f), Statement::Synchronous(stmt) => seek_stmt(heap, stmt.body, f), _ => None }; matched } fn seek_expr_in_expr bool>(heap: &Heap, start: ExpressionId, f: &F) -> Option { let expr = &heap[start]; if f(expr) { return Some(start); } match expr { Expression::Assignment(expr) => { None .or_else(|| seek_expr_in_expr(heap, expr.left, f)) .or_else(|| seek_expr_in_expr(heap, expr.right, f)) }, Expression::Conditional(expr) => { None .or_else(|| seek_expr_in_expr(heap, expr.test, f)) .or_else(|| seek_expr_in_expr(heap, expr.true_expression, f)) .or_else(|| seek_expr_in_expr(heap, expr.false_expression, f)) }, Expression::Binary(expr) => { None .or_else(|| seek_expr_in_expr(heap, expr.left, f)) .or_else(|| seek_expr_in_expr(heap, expr.right, f)) }, Expression::Unary(expr) => { seek_expr_in_expr(heap, expr.expression, f) }, Expression::Indexing(expr) => { None .or_else(|| seek_expr_in_expr(heap, expr.subject, f)) .or_else(|| seek_expr_in_expr(heap, expr.index, f)) }, Expression::Slicing(expr) => { None .or_else(|| seek_expr_in_expr(heap, expr.subject, f)) .or_else(|| seek_expr_in_expr(heap, expr.from_index, f)) .or_else(|| seek_expr_in_expr(heap, expr.to_index, f)) }, Expression::Select(expr) => { seek_expr_in_expr(heap, expr.subject, f) }, Expression::Array(expr) => { for element in &expr.elements { if let Some(id) = seek_expr_in_expr(heap, *element, f) { return Some(id) } } None }, Expression::Literal(expr) => { if let Literal::Struct(lit) = &expr.value { for field in &lit.fields { if let Some(id) = seek_expr_in_expr(heap, field.value, f) { return Some(id) } } } None }, Expression::Call(expr) => { for arg in &expr.arguments { if let Some(id) = seek_expr_in_expr(heap, *arg, f) { return Some(id) } } None }, Expression::Variable(expr) => { None } } } fn seek_expr_in_stmt bool>(heap: &Heap, start: StatementId, f: &F) -> Option { let stmt = &heap[start]; match stmt { Statement::Block(stmt) => { for stmt_id in &stmt.statements { if let Some(id) = seek_expr_in_stmt(heap, *stmt_id, f) { return Some(id) } } None }, Statement::Labeled(stmt) => { seek_expr_in_stmt(heap, stmt.body, f) }, Statement::If(stmt) => { None .or_else(|| seek_expr_in_expr(heap, stmt.test, f)) .or_else(|| seek_expr_in_stmt(heap, stmt.true_body, f)) .or_else(|| seek_expr_in_stmt(heap, stmt.false_body, f)) }, Statement::While(stmt) => { None .or_else(|| seek_expr_in_expr(heap, stmt.test, f)) .or_else(|| seek_expr_in_stmt(heap, stmt.body, f)) }, Statement::Synchronous(stmt) => { seek_expr_in_stmt(heap, stmt.body, f) }, Statement::Return(stmt) => { seek_expr_in_expr(heap, stmt.expression, f) }, Statement::Assert(stmt) => { seek_expr_in_expr(heap, stmt.expression, f) }, Statement::New(stmt) => { seek_expr_in_expr(heap, stmt.expression.upcast(), f) }, Statement::Expression(stmt) => { seek_expr_in_expr(heap, stmt.expression, f) }, _ => None } }