| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use crate::bind_library; |
| use crate::bind_program::{self, Condition, ConditionOp, Statement}; |
| use crate::dependency_graph::{self, DependencyGraph}; |
| use crate::errors::UserError; |
| use crate::instruction; |
| use crate::make_identifier; |
| use crate::offline_debugger::AstLocation; |
| use crate::parser_common::{self, CompoundIdentifier, Include, Value}; |
| use std::collections::HashMap; |
| use std::convert::TryFrom; |
| use std::fmt; |
| use std::ops::Deref; |
| use thiserror::Error; |
| |
| #[derive(Debug, Error, Clone, PartialEq)] |
| pub enum CompilerError { |
| BindParserError(parser_common::BindParserError), |
| DependencyError(dependency_graph::DependencyError<CompoundIdentifier>), |
| DuplicateIdentifier(CompoundIdentifier), |
| TypeMismatch(CompoundIdentifier), |
| UnresolvedQualification(CompoundIdentifier), |
| UndeclaredKey(CompoundIdentifier), |
| MissingExtendsKeyword(CompoundIdentifier), |
| InvalidExtendsKeyword(CompoundIdentifier), |
| UnknownKey(CompoundIdentifier), |
| IfStatementMustBeTerminal, |
| } |
| |
| impl fmt::Display for CompilerError { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| write!(f, "{}", UserError::from(self.clone())) |
| } |
| } |
| |
| pub type SymbolTable = HashMap<CompoundIdentifier, Symbol>; |
| |
| pub fn compile( |
| program: &str, |
| libraries: &[String], |
| ) -> Result<Vec<instruction::InstructionInfo>, CompilerError> { |
| let (symbolic_instructions, _) = compile_to_symbolic(program, libraries)?; |
| Ok(symbolic_instructions.into_iter().map(|symbolic| symbolic.to_instruction()).collect()) |
| } |
| |
| pub fn compile_to_symbolic<'a>( |
| program_str: &'a str, |
| libraries: &[String], |
| ) -> Result<(Vec<SymbolicInstructionInfo<'a>>, SymbolTable), CompilerError> { |
| let ast = bind_program::Ast::try_from(program_str).map_err(CompilerError::BindParserError)?; |
| |
| let library_asts: Vec<bind_library::Ast> = libraries |
| .into_iter() |
| .map(|lib| { |
| bind_library::Ast::try_from(lib.as_str()).map_err(CompilerError::BindParserError) |
| }) |
| .collect::<Result<_, CompilerError>>()?; |
| |
| let dependencies = resolve_dependencies(&ast, library_asts.iter())?; |
| let symbol_table = construct_symbol_table(dependencies.into_iter())?; |
| let symbolic_instructions = compile_statements(ast.statements, &symbol_table)?; |
| |
| Ok((symbolic_instructions, symbol_table)) |
| } |
| |
| fn resolve_dependencies<'a>( |
| program: &bind_program::Ast, |
| libraries: impl Iterator<Item = &'a bind_library::Ast> + Clone, |
| ) -> Result<Vec<&'a bind_library::Ast>, CompilerError> { |
| (|| { |
| let mut graph = DependencyGraph::new(); |
| |
| for library in libraries.clone() { |
| graph.insert_node(library.name.clone(), library); |
| } |
| |
| for Include { name, .. } in &program.using { |
| graph.insert_edge_from_root(name)?; |
| } |
| |
| for from in libraries { |
| for to in &from.using { |
| graph.insert_edge(&from.name, &to.name)?; |
| } |
| } |
| |
| graph.resolve() |
| })() |
| .map_err(CompilerError::DependencyError) |
| } |
| |
| #[allow(dead_code)] |
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| pub enum Symbol { |
| DeprecatedKey(u32), |
| Key(String, bind_library::ValueType), |
| NumberValue(u64), |
| StringValue(String), |
| BoolValue(bool), |
| EnumValue, |
| } |
| |
| impl Symbol { |
| #[allow(dead_code)] |
| pub fn to_bytecode(&self) -> u32 { |
| // We can only support numeric values until the bytecode representation is changed to handle |
| // strings. |
| match self { |
| Symbol::DeprecatedKey(value) => *value, |
| Symbol::NumberValue(value64) => match u32::try_from(*value64) { |
| Ok(value32) => value32, |
| _ => { |
| unimplemented!("64 bit values are unsupported"); |
| } |
| }, |
| _ => unimplemented!("Unsupported symbol"), |
| } |
| } |
| } |
| |
| /// Find the namespace of a qualified identifier from the library's includes. Or, if the identifier |
| /// is unqualified, return the local qualified identifier. |
| fn find_qualified_identifier( |
| declaration: &bind_library::Declaration, |
| using: &Vec<parser_common::Include>, |
| local_qualified: &CompoundIdentifier, |
| ) -> Result<CompoundIdentifier, CompilerError> { |
| if let Some(namespace) = declaration.identifier.parent() { |
| // A declaration of a qualified (i.e. non-local) key must be an extension. |
| if !declaration.extends { |
| return Err(CompilerError::MissingExtendsKeyword(declaration.identifier.clone())); |
| } |
| |
| // Special case for deprecated symbols (currently in the fuchsia namespace), return the |
| // declaration as-is. |
| if namespace == make_identifier!["fuchsia"] { |
| return Ok(declaration.identifier.clone()); |
| } |
| |
| // Find the fully qualified name from the included libraries. |
| let include = using |
| .iter() |
| .find(|include| { |
| namespace == include.name || Some(namespace.to_string()) == include.alias |
| }) |
| .ok_or(CompilerError::UnresolvedQualification(declaration.identifier.clone()))?; |
| |
| return Ok(include.name.nest(declaration.identifier.name.clone())); |
| } |
| |
| // It is not valid to extend an unqualified (i.e. local) key. |
| if declaration.extends { |
| return Err(CompilerError::InvalidExtendsKeyword(local_qualified.clone())); |
| } |
| |
| // An unqualified/local key is scoped to the current library. |
| Ok(local_qualified.clone()) |
| } |
| |
| /// Construct a map of every key and value defined by `libraries`. The identifiers in the symbol |
| /// table will be fully qualified, i.e. they will contain their full namespace. A symbol is |
| /// namespaced according to the name of the library it is defined in. If a library defines a value |
| /// by extending a previously defined key, then that value will be namespaced to the current library |
| /// and not the library of its key. |
| #[allow(dead_code)] |
| fn construct_symbol_table( |
| libraries: impl Iterator<Item = impl Deref<Target = bind_library::Ast>>, |
| ) -> Result<SymbolTable, CompilerError> { |
| let mut symbol_table = get_deprecated_symbols(); |
| for lib in libraries { |
| let bind_library::Ast { name, using, declarations } = &*lib; |
| |
| for declaration in declarations { |
| // Construct a qualified identifier for this key that's namespaced to the current |
| // library, discarding any other qualifiers. This identifier is used to scope values |
| // defined under this key. |
| let local_qualified = name.nest(declaration.identifier.name.clone()); |
| |
| // Attempt to match the namespace of the key to an include of the current library, or if |
| // it is unqualified use the local qualified name. Also do a first pass at checking |
| // whether the extend keyword is used correctly. |
| let qualified = find_qualified_identifier(declaration, using, &local_qualified)?; |
| |
| // Type-check the qualified name against the existing symbols, and check that extended |
| // keys are previously defined and that non-extended keys are not. |
| match symbol_table.get(&qualified) { |
| Some(Symbol::Key(_, value_type)) => { |
| if !declaration.extends { |
| return Err(CompilerError::DuplicateIdentifier(qualified)); |
| } |
| if declaration.value_type != *value_type { |
| return Err(CompilerError::TypeMismatch(qualified)); |
| } |
| } |
| Some(Symbol::DeprecatedKey(_)) => (), |
| Some(_) => { |
| return Err(CompilerError::TypeMismatch(qualified)); |
| } |
| None => { |
| if declaration.extends { |
| return Err(CompilerError::UndeclaredKey(qualified)); |
| } |
| symbol_table.insert( |
| qualified.clone(), |
| Symbol::Key(qualified.to_string(), declaration.value_type), |
| ); |
| } |
| } |
| |
| // Insert each value associated with the declaration into the symbol table, taking care |
| // to scope each identifier under the locally qualified identifier of the key. We don't |
| // need to type-check values here since the parser has already done that. |
| for value in &declaration.values { |
| let qualified_value = local_qualified.nest(value.identifier().to_string()); |
| if symbol_table.contains_key(&qualified_value) { |
| return Err(CompilerError::DuplicateIdentifier(qualified_value)); |
| } |
| |
| match value { |
| bind_library::Value::Number(_, value) => { |
| symbol_table.insert(qualified_value, Symbol::NumberValue(*value)); |
| } |
| bind_library::Value::Str(_, value) => { |
| symbol_table.insert(qualified_value, Symbol::StringValue(value.clone())); |
| } |
| bind_library::Value::Bool(_, value) => { |
| symbol_table.insert(qualified_value, Symbol::BoolValue(*value)); |
| } |
| bind_library::Value::Enum(_) => { |
| symbol_table.insert(qualified_value, Symbol::EnumValue); |
| } |
| }; |
| } |
| } |
| } |
| |
| Ok(symbol_table) |
| } |
| |
| /// Hard code these symbols during the migration from macros to bind programs. Eventually these |
| /// will be defined in libraries and the compiler will emit strings for them in the bytecode. |
| fn deprecated_keys() -> Vec<(String, u32)> { |
| let mut keys = Vec::new(); |
| |
| keys.push(("BIND_PROTOCOL".to_string(), 0x0001)); |
| |
| keys.push(("BIND_AUTOBIND".to_string(), 0x0002)); |
| |
| keys.push(("BIND_PLATFORM_DEV_VID".to_string(), 0x0300)); |
| keys.push(("BIND_PCI_VID".to_string(), 0x0100)); |
| |
| keys.push(("BIND_PCI_DID".to_string(), 0x0101)); |
| keys.push(("BIND_PCI_CLASS".to_string(), 0x0102)); |
| keys.push(("BIND_PCI_SUBCLASS".to_string(), 0x0103)); |
| keys.push(("BIND_PCI_INTERFACE".to_string(), 0x0104)); |
| keys.push(("BIND_PCI_REVISION".to_string(), 0x0105)); |
| |
| // usb binding variables at 0x02XX |
| // these are used for both ZX_PROTOCOL_USB_INTERFACE and ZX_PROTOCOL_USB_FUNCTION |
| keys.push(("BIND_USB_VID".to_string(), 0x0200)); |
| keys.push(("BIND_USB_PID".to_string(), 0x0201)); |
| keys.push(("BIND_USB_CLASS".to_string(), 0x0202)); |
| keys.push(("BIND_USB_SUBCLASS".to_string(), 0x0203)); |
| keys.push(("BIND_USB_PROTOCOL".to_string(), 0x0204)); |
| |
| // Platform bus binding variables at 0x03XX |
| keys.push(("BIND_PLATFORM_DEV_VID".to_string(), 0x0300)); |
| keys.push(("BIND_PLATFORM_DEV_PID".to_string(), 0x0301)); |
| keys.push(("BIND_PLATFORM_DEV_DID".to_string(), 0x0302)); |
| keys.push(("BIND_PLATFORM_PROTO".to_string(), 0x0303)); |
| keys.push(("BIND_PLATFORM_DEV_INSTANCE_ID".to_string(), 0x0304)); |
| |
| // ACPI binding variables at 0x04XX |
| // The _HID is a 7- or 8-byte string. Because a bind property is 32-bit, use 2 |
| // properties to bind using the _HID. They are encoded in big endian order for |
| // human readability. In the case of 7-byte _HID's, the 8th-byte shall be 0. |
| keys.push(("BIND_ACPI_HID_0_3".to_string(), 0x0400)); |
| keys.push(("BIND_ACPI_HID_4_7".to_string(), 0x0401)); |
| // The _CID may be a valid HID value or a bus-specific string. The ACPI bus |
| // driver only publishes those that are valid HID values. |
| keys.push(("BIND_ACPI_CID_0_3".to_string(), 0x0402)); |
| keys.push(("BIND_ACPI_CID_4_7".to_string(), 0x0403)); |
| |
| // Intel HDA Codec binding variables at 0x05XX |
| keys.push(("BIND_IHDA_CODEC_VID".to_string(), 0x0500)); |
| keys.push(("BIND_IHDA_CODEC_DID".to_string(), 0x0501)); |
| keys.push(("BIND_IHDA_CODEC_MAJOR_REV".to_string(), 0x0502)); |
| keys.push(("BIND_IHDA_CODEC_MINOR_REV".to_string(), 0x0503)); |
| keys.push(("BIND_IHDA_CODEC_VENDOR_REV".to_string(), 0x0504)); |
| keys.push(("BIND_IHDA_CODEC_VENDOR_STEP".to_string(), 0x0505)); |
| |
| // Serial binding variables at 0x06XX |
| keys.push(("BIND_SERIAL_CLASS".to_string(), 0x0600)); |
| keys.push(("BIND_SERIAL_VID".to_string(), 0x0601)); |
| keys.push(("BIND_SERIAL_PID".to_string(), 0x0602)); |
| |
| // NAND binding variables at 0x07XX |
| keys.push(("BIND_NAND_CLASS".to_string(), 0x0700)); |
| |
| // Bluetooth binding variables at 0x08XX |
| keys.push(("BIND_BT_GATT_SVC_UUID16".to_string(), 0x0800)); |
| // 128-bit UUID is split across 4 32-bit unsigned ints |
| keys.push(("BIND_BT_GATT_SVC_UUID128_1".to_string(), 0x0801)); |
| keys.push(("BIND_BT_GATT_SVC_UUID128_2".to_string(), 0x0802)); |
| keys.push(("BIND_BT_GATT_SVC_UUID128_3".to_string(), 0x0803)); |
| keys.push(("BIND_BT_GATT_SVC_UUID128_4".to_string(), 0x0804)); |
| |
| // SDIO binding variables at 0x09XX |
| keys.push(("BIND_SDIO_VID".to_string(), 0x0900)); |
| keys.push(("BIND_SDIO_PID".to_string(), 0x0901)); |
| keys.push(("BIND_SDIO_FUNCTION".to_string(), 0x0902)); |
| |
| // I2C binding variables at 0x0A0X |
| keys.push(("BIND_I2C_CLASS".to_string(), 0x0A00)); |
| keys.push(("BIND_I2C_BUS_ID".to_string(), 0x0A01)); |
| keys.push(("BIND_I2C_ADDRESS".to_string(), 0x0A02)); |
| |
| // GPIO binding variables at 0x0A1X |
| keys.push(("BIND_GPIO_PIN".to_string(), 0x0A10)); |
| |
| // POWER binding variables at 0x0A2X |
| keys.push(("BIND_POWER_DOMAIN".to_string(), 0x0A20)); |
| keys.push(("BIND_POWER_DOMAIN_COMPOSITE".to_string(), 0x0A21)); |
| |
| // POWER binding variables at 0x0A3X |
| keys.push(("BIND_CLOCK_ID".to_string(), 0x0A30)); |
| |
| // SPI binding variables at 0x0A4X |
| keys.push(("BIND_SPI_CLASS".to_string(), 0x0A40)); |
| keys.push(("BIND_SPI_BUS_ID".to_string(), 0x0A41)); |
| keys.push(("BIND_SPI_CHIP_SELECT".to_string(), 0x0A42)); |
| |
| // Registers binding variables at 0x0A8X |
| keys.push(("BIND_REGISTER_ID".to_string(), 0x0A80)); |
| |
| // Fuchsia-defined topological path properties are at 0x0B00 through 0x0B7F. |
| // Vendor-defined topological path properties are at 0x0B80 to 0x0BFF. |
| // For vendor properties, it is recommended that a vendor ID be included |
| // and checked via some other property. |
| keys.push(("BIND_TOPO_START".to_string(), 0x0B00)); |
| keys.push(("BIND_TOPO_PCI".to_string(), 0x0B00)); |
| keys.push(("BIND_TOPO_I2C".to_string(), 0x0B01)); |
| keys.push(("BIND_TOPO_SPI".to_string(), 0x0B02)); |
| keys.push(("BIND_TOPO_VENDOR_START".to_string(), 0x0B80)); |
| keys.push(("BIND_TOPO_VENDOR_END".to_string(), 0x0BFF)); |
| keys.push(("BIND_TOPO_END".to_string(), 0x0BFF)); |
| |
| keys |
| } |
| |
| fn get_deprecated_symbols() -> SymbolTable { |
| let mut symbol_table = HashMap::new(); |
| for (key, value) in deprecated_keys() { |
| symbol_table.insert(make_identifier!("fuchsia", key), Symbol::DeprecatedKey(value)); |
| } |
| symbol_table |
| } |
| |
| pub fn get_deprecated_key_identifiers() -> HashMap<u32, String> { |
| let mut key_identifiers = HashMap::new(); |
| for (key, value) in deprecated_keys() { |
| key_identifiers.insert(value, make_identifier!("fuchsia", key).to_string()); |
| } |
| key_identifiers |
| } |
| |
| #[derive(Debug, PartialEq)] |
| pub struct SymbolicInstructionInfo<'a> { |
| pub location: Option<AstLocation<'a>>, |
| pub instruction: SymbolicInstruction, |
| } |
| |
| impl<'a> SymbolicInstructionInfo<'a> { |
| pub fn to_instruction(self) -> instruction::InstructionInfo { |
| instruction::InstructionInfo { |
| instruction: self.instruction.to_instruction(), |
| debug: match self.location { |
| Some(location) => location.to_instruction_debug(), |
| None => instruction::InstructionDebug::none(), |
| }, |
| } |
| } |
| } |
| |
| #[derive(Debug, PartialEq)] |
| pub enum SymbolicInstruction { |
| AbortIfEqual { lhs: Symbol, rhs: Symbol }, |
| AbortIfNotEqual { lhs: Symbol, rhs: Symbol }, |
| Label(u32), |
| UnconditionalJump { label: u32 }, |
| JumpIfEqual { lhs: Symbol, rhs: Symbol, label: u32 }, |
| JumpIfNotEqual { lhs: Symbol, rhs: Symbol, label: u32 }, |
| UnconditionalAbort, |
| UnconditionalBind, |
| } |
| |
| impl SymbolicInstruction { |
| pub fn to_instruction(self) -> instruction::Instruction { |
| match self { |
| SymbolicInstruction::AbortIfEqual { lhs, rhs } => instruction::Instruction::Abort( |
| instruction::Condition::Equal(lhs.to_bytecode(), rhs.to_bytecode()), |
| ), |
| SymbolicInstruction::AbortIfNotEqual { lhs, rhs } => instruction::Instruction::Abort( |
| instruction::Condition::NotEqual(lhs.to_bytecode(), rhs.to_bytecode()), |
| ), |
| SymbolicInstruction::Label(label_id) => instruction::Instruction::Label(label_id), |
| SymbolicInstruction::UnconditionalJump { label } => { |
| instruction::Instruction::Goto(instruction::Condition::Always, label) |
| } |
| SymbolicInstruction::JumpIfEqual { lhs, rhs, label } => instruction::Instruction::Goto( |
| instruction::Condition::Equal(lhs.to_bytecode(), rhs.to_bytecode()), |
| label, |
| ), |
| SymbolicInstruction::JumpIfNotEqual { lhs, rhs, label } => { |
| instruction::Instruction::Goto( |
| instruction::Condition::NotEqual(lhs.to_bytecode(), rhs.to_bytecode()), |
| label, |
| ) |
| } |
| SymbolicInstruction::UnconditionalAbort => { |
| instruction::Instruction::Abort(instruction::Condition::Always) |
| } |
| SymbolicInstruction::UnconditionalBind => { |
| instruction::Instruction::Match(instruction::Condition::Always) |
| } |
| } |
| } |
| } |
| |
| pub fn compile_statements<'a>( |
| statements: Vec<Statement<'a>>, |
| symbol_table: &SymbolTable, |
| ) -> Result<Vec<SymbolicInstructionInfo<'a>>, CompilerError> { |
| let mut compiler = Compiler::new(symbol_table); |
| compiler.compile_statements(statements)?; |
| Ok(compiler.instructions) |
| } |
| |
| struct Compiler<'a, 'b> { |
| instructions: Vec<SymbolicInstructionInfo<'a>>, |
| next_label_id: u32, |
| symbol_table: &'b SymbolTable, |
| } |
| |
| impl<'a, 'b> Compiler<'a, 'b> { |
| fn new(symbol_table: &'b SymbolTable) -> Self { |
| Compiler { instructions: vec![], next_label_id: 0, symbol_table } |
| } |
| |
| fn lookup_identifier(&self, identifier: &CompoundIdentifier) -> Result<Symbol, CompilerError> { |
| let symbol = self |
| .symbol_table |
| .get(identifier) |
| .ok_or(CompilerError::UnknownKey(identifier.clone()))?; |
| Ok(symbol.clone()) |
| } |
| |
| fn lookup_value(&self, value: &Value) -> Result<Symbol, CompilerError> { |
| match value { |
| Value::NumericLiteral(n) => Ok(Symbol::NumberValue(*n)), |
| Value::StringLiteral(s) => Ok(Symbol::StringValue(s.to_string())), |
| Value::BoolLiteral(b) => Ok(Symbol::BoolValue(*b)), |
| Value::Identifier(ident) => self |
| .symbol_table |
| .get(ident) |
| .ok_or(CompilerError::UnknownKey(ident.clone())) |
| .map(|x| x.clone()), |
| } |
| } |
| |
| fn compile_statements(&mut self, statements: Vec<Statement<'a>>) -> Result<(), CompilerError> { |
| self.compile_block(statements)?; |
| |
| // If none of the statements caused an abort, then we should bind the driver. |
| self.instructions.push(SymbolicInstructionInfo { |
| location: None, |
| instruction: SymbolicInstruction::UnconditionalBind, |
| }); |
| |
| Ok(()) |
| } |
| |
| fn get_unique_label(&mut self) -> u32 { |
| let label = self.next_label_id; |
| self.next_label_id += 1; |
| label |
| } |
| |
| fn compile_block(&mut self, statements: Vec<Statement<'a>>) -> Result<(), CompilerError> { |
| let mut iter = statements.into_iter().peekable(); |
| while let Some(statement) = iter.next() { |
| match statement { |
| Statement::ConditionStatement { .. } => { |
| if let Statement::ConditionStatement { |
| span: _, |
| condition: Condition { span: _, lhs, op, rhs }, |
| } = &statement |
| { |
| let lhs_symbol = self.lookup_identifier(lhs)?; |
| let rhs_symbol = self.lookup_value(rhs)?; |
| let instruction = match op { |
| ConditionOp::Equals => SymbolicInstruction::AbortIfNotEqual { |
| lhs: lhs_symbol, |
| rhs: rhs_symbol, |
| }, |
| ConditionOp::NotEquals => SymbolicInstruction::AbortIfEqual { |
| lhs: lhs_symbol, |
| rhs: rhs_symbol, |
| }, |
| }; |
| self.instructions.push(SymbolicInstructionInfo { |
| location: Some(AstLocation::ConditionStatement(statement)), |
| instruction, |
| }); |
| } |
| } |
| Statement::Accept { span, identifier, values } => { |
| let lhs_symbol = self.lookup_identifier(&identifier)?; |
| let label_id = self.get_unique_label(); |
| for value in values { |
| self.instructions.push(SymbolicInstructionInfo { |
| location: Some(AstLocation::AcceptStatementValue { |
| identifier: identifier.clone(), |
| value: value.clone(), |
| span: span.clone(), |
| }), |
| instruction: SymbolicInstruction::JumpIfEqual { |
| lhs: lhs_symbol.clone(), |
| rhs: self.lookup_value(&value)?, |
| label: label_id, |
| }, |
| }); |
| } |
| self.instructions.push(SymbolicInstructionInfo { |
| location: Some(AstLocation::AcceptStatementFailure { |
| identifier, |
| symbol: lhs_symbol, |
| span, |
| }), |
| instruction: SymbolicInstruction::UnconditionalAbort, |
| }); |
| self.instructions.push(SymbolicInstructionInfo { |
| location: None, |
| instruction: SymbolicInstruction::Label(label_id), |
| }); |
| } |
| Statement::If { span: _, blocks, else_block } => { |
| if !iter.peek().is_none() { |
| return Err(CompilerError::IfStatementMustBeTerminal); |
| } |
| |
| let final_label_id = self.get_unique_label(); |
| |
| for (condition, block_statements) in blocks { |
| let Condition { span: _, lhs, op, rhs } = &condition; |
| |
| let lhs_symbol = self.lookup_identifier(lhs)?; |
| let rhs_symbol = self.lookup_value(rhs)?; |
| |
| // Generate instructions for the condition. |
| let label_id = self.get_unique_label(); |
| let instruction = match op { |
| ConditionOp::Equals => SymbolicInstruction::JumpIfNotEqual { |
| lhs: lhs_symbol, |
| rhs: rhs_symbol, |
| label: label_id, |
| }, |
| ConditionOp::NotEquals => SymbolicInstruction::JumpIfEqual { |
| lhs: lhs_symbol, |
| rhs: rhs_symbol, |
| label: label_id, |
| }, |
| }; |
| self.instructions.push(SymbolicInstructionInfo { |
| location: Some(AstLocation::IfCondition(condition)), |
| instruction, |
| }); |
| |
| // Compile the block itself. |
| self.compile_block(block_statements)?; |
| |
| // Jump to after the if statement. |
| self.instructions.push(SymbolicInstructionInfo { |
| location: None, |
| instruction: SymbolicInstruction::UnconditionalJump { |
| label: final_label_id, |
| }, |
| }); |
| |
| // Insert a label to jump to when the condition fails. |
| self.instructions.push(SymbolicInstructionInfo { |
| location: None, |
| instruction: SymbolicInstruction::Label(label_id), |
| }); |
| } |
| |
| // Compile the else block. |
| self.compile_block(else_block)?; |
| |
| // Insert a label to jump to at the end of the whole if statement. Note that we |
| // could just emit an unconditional bind instead of jumping, since we know that |
| // if statements are terminal, but we do the jump to be consistent with |
| // condition and accept statements. |
| self.instructions.push(SymbolicInstructionInfo { |
| location: None, |
| instruction: SymbolicInstruction::Label(final_label_id), |
| }); |
| } |
| Statement::Abort { span: _ } => { |
| self.instructions.push(SymbolicInstructionInfo { |
| location: Some(AstLocation::AbortStatement(statement)), |
| instruction: SymbolicInstruction::UnconditionalAbort, |
| }); |
| } |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| use crate::parser_common::Span; |
| |
| mod symbol_table { |
| use super::*; |
| |
| #[test] |
| fn simple_key_and_value() { |
| let libraries = vec![bind_library::Ast { |
| name: make_identifier!("test"), |
| using: vec![], |
| declarations: vec![bind_library::Declaration { |
| identifier: make_identifier!["symbol"], |
| value_type: bind_library::ValueType::Number, |
| extends: false, |
| values: vec![(bind_library::Value::Number("x".to_string(), 1))], |
| }], |
| }]; |
| |
| let st = construct_symbol_table(libraries.iter()).unwrap(); |
| assert_eq!( |
| st.get(&make_identifier!("test", "symbol")), |
| Some(&Symbol::Key("test.symbol".to_string(), bind_library::ValueType::Number)) |
| ); |
| assert_eq!( |
| st.get(&make_identifier!("test", "symbol", "x")), |
| Some(&Symbol::NumberValue(1)) |
| ); |
| } |
| |
| #[test] |
| fn extension() { |
| let libraries = vec![ |
| bind_library::Ast { |
| name: make_identifier!("lib_a"), |
| using: vec![], |
| declarations: vec![bind_library::Declaration { |
| identifier: make_identifier!["symbol"], |
| value_type: bind_library::ValueType::Number, |
| extends: false, |
| values: vec![(bind_library::Value::Number("x".to_string(), 1))], |
| }], |
| }, |
| bind_library::Ast { |
| name: make_identifier!("lib_b"), |
| using: vec![Include { name: make_identifier!("lib_a"), alias: None }], |
| declarations: vec![bind_library::Declaration { |
| identifier: make_identifier!["lib_a", "symbol"], |
| value_type: bind_library::ValueType::Number, |
| extends: true, |
| values: vec![(bind_library::Value::Number("y".to_string(), 2))], |
| }], |
| }, |
| ]; |
| |
| let st = construct_symbol_table(libraries.iter()).unwrap(); |
| assert_eq!( |
| st.get(&make_identifier!("lib_a", "symbol")), |
| Some(&Symbol::Key("lib_a.symbol".to_string(), bind_library::ValueType::Number)) |
| ); |
| assert_eq!( |
| st.get(&make_identifier!("lib_a", "symbol", "x")), |
| Some(&Symbol::NumberValue(1)) |
| ); |
| assert_eq!( |
| st.get(&make_identifier!("lib_b", "symbol", "y")), |
| Some(&Symbol::NumberValue(2)) |
| ); |
| } |
| |
| #[test] |
| fn aliased_extension() { |
| let libraries = vec![ |
| bind_library::Ast { |
| name: make_identifier!("lib_a"), |
| using: vec![], |
| declarations: vec![bind_library::Declaration { |
| identifier: make_identifier!["symbol"], |
| value_type: bind_library::ValueType::Number, |
| extends: false, |
| values: vec![(bind_library::Value::Number("x".to_string(), 1))], |
| }], |
| }, |
| bind_library::Ast { |
| name: make_identifier!("lib_b"), |
| using: vec![Include { |
| name: make_identifier!("lib_a"), |
| alias: Some("alias".to_string()), |
| }], |
| declarations: vec![bind_library::Declaration { |
| identifier: make_identifier!["alias", "symbol"], |
| value_type: bind_library::ValueType::Number, |
| extends: true, |
| values: vec![(bind_library::Value::Number("y".to_string(), 2))], |
| }], |
| }, |
| ]; |
| |
| let st = construct_symbol_table(libraries.iter()).unwrap(); |
| assert_eq!( |
| st.get(&make_identifier!("lib_a", "symbol")), |
| Some(&Symbol::Key("lib_a.symbol".to_string(), bind_library::ValueType::Number)) |
| ); |
| assert_eq!( |
| st.get(&make_identifier!("lib_a", "symbol", "x")), |
| Some(&Symbol::NumberValue(1)) |
| ); |
| assert_eq!( |
| st.get(&make_identifier!("lib_b", "symbol", "y")), |
| Some(&Symbol::NumberValue(2)) |
| ); |
| } |
| |
| #[test] |
| fn deprecated_key_extension() { |
| let libraries = vec![bind_library::Ast { |
| name: make_identifier!("lib_a"), |
| using: vec![], |
| declarations: vec![bind_library::Declaration { |
| identifier: make_identifier!["fuchsia", "BIND_PCI_DID"], |
| value_type: bind_library::ValueType::Number, |
| extends: true, |
| values: vec![(bind_library::Value::Number("x".to_string(), 0x1234))], |
| }], |
| }]; |
| |
| let st = construct_symbol_table(libraries.iter()).unwrap(); |
| assert_eq!( |
| st.get(&make_identifier!("lib_a", "BIND_PCI_DID", "x")), |
| Some(&Symbol::NumberValue(0x1234)) |
| ); |
| } |
| |
| #[test] |
| fn duplicate_key() { |
| let libraries = vec![bind_library::Ast { |
| name: make_identifier!("test"), |
| using: vec![], |
| declarations: vec![ |
| bind_library::Declaration { |
| identifier: make_identifier!["symbol"], |
| value_type: bind_library::ValueType::Number, |
| extends: false, |
| values: vec![], |
| }, |
| bind_library::Declaration { |
| identifier: make_identifier!["symbol"], |
| value_type: bind_library::ValueType::Number, |
| extends: false, |
| values: vec![], |
| }, |
| ], |
| }]; |
| |
| assert_eq!( |
| construct_symbol_table(libraries.iter()), |
| Err(CompilerError::DuplicateIdentifier(make_identifier!("test", "symbol"))) |
| ); |
| } |
| |
| #[test] |
| fn duplicate_value() { |
| let libraries = vec![bind_library::Ast { |
| name: make_identifier!("test"), |
| using: vec![], |
| declarations: vec![bind_library::Declaration { |
| identifier: make_identifier!["symbol"], |
| value_type: bind_library::ValueType::Number, |
| extends: false, |
| values: vec![ |
| bind_library::Value::Number("a".to_string(), 1), |
| bind_library::Value::Number("a".to_string(), 2), |
| ], |
| }], |
| }]; |
| |
| assert_eq!( |
| construct_symbol_table(libraries.iter()), |
| Err(CompilerError::DuplicateIdentifier(make_identifier!("test", "symbol", "a"))) |
| ); |
| } |
| |
| #[test] |
| fn keys_are_qualified() { |
| // The same symbol declared in two libraries should not collide. |
| let libraries = vec![ |
| bind_library::Ast { |
| name: make_identifier!("lib_a"), |
| using: vec![], |
| declarations: vec![bind_library::Declaration { |
| identifier: make_identifier!["symbol"], |
| value_type: bind_library::ValueType::Number, |
| extends: false, |
| values: vec![], |
| }], |
| }, |
| bind_library::Ast { |
| name: make_identifier!("lib_b"), |
| using: vec![], |
| declarations: vec![bind_library::Declaration { |
| identifier: make_identifier!["symbol"], |
| value_type: bind_library::ValueType::Number, |
| extends: false, |
| values: vec![], |
| }], |
| }, |
| ]; |
| |
| let st = construct_symbol_table(libraries.iter()).unwrap(); |
| assert_eq!( |
| st.get(&make_identifier!("lib_a", "symbol")), |
| Some(&Symbol::Key("lib_a.symbol".to_string(), bind_library::ValueType::Number)) |
| ); |
| assert_eq!( |
| st.get(&make_identifier!("lib_b", "symbol")), |
| Some(&Symbol::Key("lib_b.symbol".to_string(), bind_library::ValueType::Number)) |
| ); |
| } |
| |
| #[test] |
| fn missing_extend_keyword() { |
| // A library referring to a previously declared symbol must use the "extend" keyword. |
| let libraries = vec![ |
| bind_library::Ast { |
| name: make_identifier!("lib_a"), |
| using: vec![], |
| declarations: vec![bind_library::Declaration { |
| identifier: make_identifier!["symbol"], |
| value_type: bind_library::ValueType::Number, |
| extends: false, |
| values: vec![], |
| }], |
| }, |
| bind_library::Ast { |
| name: make_identifier!("lib_b"), |
| using: vec![], |
| declarations: vec![bind_library::Declaration { |
| identifier: make_identifier!["lib_a", "symbol"], |
| value_type: bind_library::ValueType::Number, |
| extends: false, |
| values: vec![], |
| }], |
| }, |
| ]; |
| |
| assert_eq!( |
| construct_symbol_table(libraries.iter()), |
| Err(CompilerError::MissingExtendsKeyword(make_identifier!("lib_a", "symbol"))) |
| ); |
| } |
| |
| #[test] |
| fn invalid_extend_keyword() { |
| // A library cannot declare an unqualified (and therefore locally namespaced) symbol |
| // with the "extend" keyword. |
| let libraries = vec![bind_library::Ast { |
| name: make_identifier!("lib_a"), |
| using: vec![], |
| declarations: vec![bind_library::Declaration { |
| identifier: make_identifier!["symbol"], |
| value_type: bind_library::ValueType::Number, |
| extends: true, |
| values: vec![], |
| }], |
| }]; |
| |
| assert_eq!( |
| construct_symbol_table(libraries.iter()), |
| Err(CompilerError::InvalidExtendsKeyword(make_identifier!("lib_a", "symbol"))) |
| ); |
| } |
| |
| #[test] |
| fn unresolved_qualification() { |
| // A library cannot refer to a qualified identifier where the qualifier is not in its |
| // list of includes. |
| let libraries = vec![bind_library::Ast { |
| name: make_identifier!("lib_a"), |
| using: vec![], |
| declarations: vec![bind_library::Declaration { |
| identifier: make_identifier!["lib_b", "symbol"], |
| value_type: bind_library::ValueType::Number, |
| extends: true, |
| values: vec![], |
| }], |
| }]; |
| |
| assert_eq!( |
| construct_symbol_table(libraries.iter()), |
| Err(CompilerError::UnresolvedQualification(make_identifier!("lib_b", "symbol"))) |
| ); |
| } |
| |
| #[test] |
| fn undeclared_key() { |
| let libraries = vec![ |
| bind_library::Ast { |
| name: make_identifier!("lib_a"), |
| using: vec![], |
| declarations: vec![], |
| }, |
| bind_library::Ast { |
| name: make_identifier!("lib_b"), |
| using: vec![Include { name: make_identifier!("lib_a"), alias: None }], |
| declarations: vec![bind_library::Declaration { |
| identifier: make_identifier!["lib_a", "symbol"], |
| value_type: bind_library::ValueType::Number, |
| extends: true, |
| values: vec![], |
| }], |
| }, |
| ]; |
| |
| assert_eq!( |
| construct_symbol_table(libraries.iter()), |
| Err(CompilerError::UndeclaredKey(make_identifier!("lib_a", "symbol"))) |
| ); |
| } |
| |
| #[test] |
| fn type_mismatch() { |
| let libraries = vec![ |
| bind_library::Ast { |
| name: make_identifier!("lib_a"), |
| using: vec![], |
| declarations: vec![bind_library::Declaration { |
| identifier: make_identifier!["symbol"], |
| value_type: bind_library::ValueType::Str, |
| extends: false, |
| values: vec![], |
| }], |
| }, |
| bind_library::Ast { |
| name: make_identifier!("lib_b"), |
| using: vec![Include { name: make_identifier!("lib_a"), alias: None }], |
| declarations: vec![bind_library::Declaration { |
| identifier: make_identifier!["lib_a", "symbol"], |
| value_type: bind_library::ValueType::Number, |
| extends: true, |
| values: vec![], |
| }], |
| }, |
| ]; |
| |
| assert_eq!( |
| construct_symbol_table(libraries.iter()), |
| Err(CompilerError::TypeMismatch(make_identifier!("lib_a", "symbol"))) |
| ); |
| } |
| } |
| |
| #[test] |
| fn condition() { |
| let condition_statement = Statement::ConditionStatement { |
| span: Span::new(), |
| condition: Condition { |
| span: Span::new(), |
| lhs: make_identifier!("abc"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(42), |
| }, |
| }; |
| |
| let program = |
| bind_program::Ast { using: vec![], statements: vec![condition_statement.clone()] }; |
| let mut symbol_table = HashMap::new(); |
| symbol_table.insert( |
| make_identifier!("abc"), |
| Symbol::Key("abc".to_string(), bind_library::ValueType::Number), |
| ); |
| |
| assert_eq!( |
| compile_statements(program.statements, &symbol_table), |
| Ok(vec![ |
| SymbolicInstructionInfo { |
| location: Some(AstLocation::ConditionStatement(condition_statement)), |
| instruction: SymbolicInstruction::AbortIfNotEqual { |
| lhs: Symbol::Key("abc".to_string(), bind_library::ValueType::Number), |
| rhs: Symbol::NumberValue(42) |
| } |
| }, |
| SymbolicInstructionInfo { |
| location: None, |
| instruction: SymbolicInstruction::UnconditionalBind |
| } |
| ]) |
| ); |
| } |
| |
| #[test] |
| fn accept() { |
| let program = bind_program::Ast { |
| using: vec![], |
| statements: vec![Statement::Accept { |
| span: Span::new(), |
| identifier: make_identifier!("abc"), |
| values: vec![Value::NumericLiteral(42), Value::NumericLiteral(314)], |
| }], |
| }; |
| let mut symbol_table = HashMap::new(); |
| symbol_table.insert( |
| make_identifier!("abc"), |
| Symbol::Key("abc".to_string(), bind_library::ValueType::Number), |
| ); |
| |
| assert_eq!( |
| compile_statements(program.statements, &symbol_table), |
| Ok(vec![ |
| SymbolicInstructionInfo { |
| location: Some(AstLocation::AcceptStatementValue { |
| identifier: make_identifier!("abc"), |
| value: Value::NumericLiteral(42), |
| span: Span::new() |
| }), |
| instruction: SymbolicInstruction::JumpIfEqual { |
| lhs: Symbol::Key("abc".to_string(), bind_library::ValueType::Number), |
| rhs: Symbol::NumberValue(42), |
| label: 0 |
| } |
| }, |
| SymbolicInstructionInfo { |
| location: Some(AstLocation::AcceptStatementValue { |
| identifier: make_identifier!("abc"), |
| value: Value::NumericLiteral(314), |
| span: Span::new() |
| }), |
| instruction: SymbolicInstruction::JumpIfEqual { |
| lhs: Symbol::Key("abc".to_string(), bind_library::ValueType::Number), |
| rhs: Symbol::NumberValue(314), |
| label: 0 |
| } |
| }, |
| SymbolicInstructionInfo { |
| location: Some(AstLocation::AcceptStatementFailure { |
| identifier: make_identifier!("abc"), |
| symbol: Symbol::Key("abc".to_string(), bind_library::ValueType::Number), |
| span: Span::new() |
| }), |
| instruction: SymbolicInstruction::UnconditionalAbort |
| }, |
| SymbolicInstructionInfo { |
| location: None, |
| instruction: SymbolicInstruction::Label(0) |
| }, |
| SymbolicInstructionInfo { |
| location: None, |
| instruction: SymbolicInstruction::UnconditionalBind |
| }, |
| ]) |
| ); |
| } |
| |
| #[test] |
| fn if_else() { |
| let condition1 = Condition { |
| span: Span::new(), |
| lhs: make_identifier!("abc"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(1), |
| }; |
| let condition2 = Condition { |
| span: Span::new(), |
| lhs: make_identifier!("abc"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(2), |
| }; |
| let statement1 = Statement::ConditionStatement { |
| span: Span::new(), |
| condition: Condition { |
| span: Span::new(), |
| lhs: make_identifier!("abc"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(2), |
| }, |
| }; |
| let statement2 = Statement::ConditionStatement { |
| span: Span::new(), |
| condition: Condition { |
| span: Span::new(), |
| lhs: make_identifier!("abc"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(3), |
| }, |
| }; |
| let statement3 = Statement::ConditionStatement { |
| span: Span::new(), |
| condition: Condition { |
| span: Span::new(), |
| lhs: make_identifier!("abc"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(3), |
| }, |
| }; |
| |
| let program = bind_program::Ast { |
| using: vec![], |
| statements: vec![Statement::If { |
| span: Span::new(), |
| blocks: vec![ |
| (condition1.clone(), vec![statement1.clone()]), |
| (condition2.clone(), vec![statement2.clone()]), |
| ], |
| else_block: vec![statement3.clone()], |
| }], |
| }; |
| let mut symbol_table = HashMap::new(); |
| symbol_table.insert( |
| make_identifier!("abc"), |
| Symbol::Key("abc".to_string(), bind_library::ValueType::Number), |
| ); |
| |
| assert_eq!( |
| compile_statements(program.statements, &symbol_table), |
| Ok(vec![ |
| SymbolicInstructionInfo { |
| location: Some(AstLocation::IfCondition(condition1)), |
| instruction: SymbolicInstruction::JumpIfNotEqual { |
| lhs: Symbol::Key("abc".to_string(), bind_library::ValueType::Number), |
| rhs: Symbol::NumberValue(1), |
| label: 1 |
| } |
| }, |
| SymbolicInstructionInfo { |
| location: Some(AstLocation::ConditionStatement(statement1)), |
| instruction: SymbolicInstruction::AbortIfNotEqual { |
| lhs: Symbol::Key("abc".to_string(), bind_library::ValueType::Number), |
| rhs: Symbol::NumberValue(2) |
| } |
| }, |
| SymbolicInstructionInfo { |
| location: None, |
| instruction: SymbolicInstruction::UnconditionalJump { label: 0 } |
| }, |
| SymbolicInstructionInfo { |
| location: None, |
| instruction: SymbolicInstruction::Label(1) |
| }, |
| SymbolicInstructionInfo { |
| location: Some(AstLocation::IfCondition(condition2)), |
| instruction: SymbolicInstruction::JumpIfNotEqual { |
| lhs: Symbol::Key("abc".to_string(), bind_library::ValueType::Number), |
| rhs: Symbol::NumberValue(2), |
| label: 2 |
| } |
| }, |
| SymbolicInstructionInfo { |
| location: Some(AstLocation::ConditionStatement(statement2)), |
| instruction: SymbolicInstruction::AbortIfNotEqual { |
| lhs: Symbol::Key("abc".to_string(), bind_library::ValueType::Number), |
| rhs: Symbol::NumberValue(3) |
| } |
| }, |
| SymbolicInstructionInfo { |
| location: None, |
| instruction: SymbolicInstruction::UnconditionalJump { label: 0 } |
| }, |
| SymbolicInstructionInfo { |
| location: None, |
| instruction: SymbolicInstruction::Label(2) |
| }, |
| SymbolicInstructionInfo { |
| location: Some(AstLocation::ConditionStatement(statement3)), |
| instruction: SymbolicInstruction::AbortIfNotEqual { |
| lhs: Symbol::Key("abc".to_string(), bind_library::ValueType::Number), |
| rhs: Symbol::NumberValue(3) |
| } |
| }, |
| SymbolicInstructionInfo { |
| location: None, |
| instruction: SymbolicInstruction::Label(0) |
| }, |
| SymbolicInstructionInfo { |
| location: None, |
| instruction: SymbolicInstruction::UnconditionalBind |
| }, |
| ]) |
| ); |
| } |
| |
| #[test] |
| fn if_else_must_be_terminal() { |
| let program = bind_program::Ast { |
| using: vec![], |
| statements: vec![ |
| Statement::If { |
| span: Span::new(), |
| blocks: vec![( |
| Condition { |
| span: Span::new(), |
| lhs: make_identifier!("abc"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(1), |
| }, |
| vec![Statement::ConditionStatement { |
| span: Span::new(), |
| condition: Condition { |
| span: Span::new(), |
| lhs: make_identifier!("abc"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(2), |
| }, |
| }], |
| )], |
| else_block: vec![Statement::ConditionStatement { |
| span: Span::new(), |
| condition: Condition { |
| span: Span::new(), |
| lhs: make_identifier!("abc"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(3), |
| }, |
| }], |
| }, |
| Statement::Accept { |
| span: Span::new(), |
| identifier: make_identifier!("abc"), |
| values: vec![Value::NumericLiteral(42), Value::NumericLiteral(314)], |
| }, |
| ], |
| }; |
| let mut symbol_table = HashMap::new(); |
| symbol_table.insert( |
| make_identifier!("abc"), |
| Symbol::Key("abc".to_string(), bind_library::ValueType::Number), |
| ); |
| |
| assert_eq!( |
| compile_statements(program.statements, &symbol_table), |
| Err(CompilerError::IfStatementMustBeTerminal) |
| ); |
| } |
| |
| #[test] |
| fn abort() { |
| let abort_statement = Statement::Abort { span: Span::new() }; |
| |
| let program = |
| bind_program::Ast { using: vec![], statements: vec![abort_statement.clone()] }; |
| let symbol_table = HashMap::new(); |
| |
| assert_eq!( |
| compile_statements(program.statements, &symbol_table), |
| Ok(vec![ |
| SymbolicInstructionInfo { |
| location: Some(AstLocation::AbortStatement(abort_statement)), |
| instruction: SymbolicInstruction::UnconditionalAbort |
| }, |
| SymbolicInstructionInfo { |
| location: None, |
| instruction: SymbolicInstruction::UnconditionalBind |
| } |
| ]) |
| ); |
| } |
| |
| #[test] |
| fn dependencies() { |
| let program = bind_program::Ast { |
| using: vec![Include { name: make_identifier!("A"), alias: None }], |
| statements: vec![], |
| }; |
| let libraries = vec![ |
| bind_library::Ast { |
| name: make_identifier!("A"), |
| using: vec![Include { name: make_identifier!("A", "B"), alias: None }], |
| declarations: vec![], |
| }, |
| bind_library::Ast { |
| name: make_identifier!("A", "B"), |
| using: vec![], |
| declarations: vec![], |
| }, |
| bind_library::Ast { |
| name: make_identifier!("A", "C"), |
| using: vec![], |
| declarations: vec![], |
| }, |
| ]; |
| |
| assert_eq!( |
| resolve_dependencies(&program, libraries.iter()), |
| Ok(vec![ |
| &bind_library::Ast { |
| name: make_identifier!("A"), |
| using: vec![Include { name: make_identifier!("A", "B"), alias: None }], |
| declarations: vec![], |
| }, |
| &bind_library::Ast { |
| name: make_identifier!("A", "B"), |
| using: vec![], |
| declarations: vec![], |
| }, |
| ]) |
| ); |
| } |
| |
| #[test] |
| fn dependencies_error() { |
| let program = bind_program::Ast { |
| using: vec![Include { name: make_identifier!("A"), alias: None }], |
| statements: vec![], |
| }; |
| let libraries = vec![ |
| bind_library::Ast { |
| name: make_identifier!("A"), |
| using: vec![Include { name: make_identifier!("A", "B"), alias: None }], |
| declarations: vec![], |
| }, |
| bind_library::Ast { |
| name: make_identifier!("A", "C"), |
| using: vec![], |
| declarations: vec![], |
| }, |
| ]; |
| |
| assert_eq!( |
| resolve_dependencies(&program, libraries.iter()), |
| Err(CompilerError::DependencyError( |
| dependency_graph::DependencyError::MissingDependency(make_identifier!("A", "B")) |
| )) |
| ); |
| } |
| } |