| // 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::parser_common::{self, CompoundIdentifier, Include, Value}; |
| use std::collections::HashMap; |
| use std::convert::TryFrom; |
| use std::fmt; |
| use std::fs::File; |
| use std::io::Read; |
| use std::ops::Deref; |
| use std::path::PathBuf; |
| use std::str::FromStr; |
| |
| #[derive(Debug, Clone, PartialEq)] |
| pub enum CompilerError { |
| FileOpenError(PathBuf), |
| FileReadError(PathBuf), |
| 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 { |
| let blah: CompilerError = self.clone(); |
| let user_error = UserError::from(blah); |
| write!(f, "{}", user_error) |
| } |
| } |
| |
| pub fn compile( |
| program: PathBuf, |
| libraries: &[PathBuf], |
| ) -> Result<Vec<instruction::Instruction>, CompilerError> { |
| let mut file = |
| File::open(&program).map_err(|_| CompilerError::FileOpenError(program.clone()))?; |
| let mut buf = String::new(); |
| file.read_to_string(&mut buf).map_err(|_| CompilerError::FileReadError(program.clone()))?; |
| let ast = bind_program::Ast::from_str(&buf).map_err(CompilerError::BindParserError)?; |
| |
| let mut library_asts = vec![]; |
| for library in libraries { |
| let mut file = |
| File::open(library).map_err(|_| CompilerError::FileOpenError(library.clone()))?; |
| let mut buf = String::new(); |
| file.read_to_string(&mut buf).map_err(|_| CompilerError::FileReadError(library.clone()))?; |
| library_asts |
| .push(bind_library::Ast::from_str(&buf).map_err(CompilerError::BindParserError)?); |
| } |
| |
| let dependencies: Vec<&bind_library::Ast> = 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.into_iter().map(|symbolic| symbolic.to_instruction()).collect()) |
| } |
| |
| 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)] |
| enum Symbol { |
| DeprecatedKey(u32), |
| Key(String, bind_library::ValueType), |
| NumberValue(u64), |
| StringValue(String), |
| BoolValue(bool), |
| EnumValue, |
| } |
| |
| impl Symbol { |
| #[allow(dead_code)] |
| 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, return the declaration as-is. |
| if namespace == make_identifier!["deprecated"] { |
| 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<HashMap<CompoundIdentifier, Symbol>, 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 get_deprecated_symbols() -> HashMap<CompoundIdentifier, Symbol> { |
| let mut symbol_table = HashMap::new(); |
| let mut insert_deprecated = |key, value| { |
| symbol_table.insert(make_identifier!("deprecated", key), Symbol::DeprecatedKey(value)); |
| }; |
| |
| insert_deprecated("BIND_PLATFORM_DEV_VID", 0x0300); |
| insert_deprecated("BIND_PCI_VID", 0x0100); |
| |
| insert_deprecated("BIND_PCI_DID", 0x0101); |
| insert_deprecated("BIND_PCI_CLASS", 0x0102); |
| insert_deprecated("BIND_PCI_SUBCLASS", 0x0103); |
| insert_deprecated("BIND_PCI_INTERFACE", 0x0104); |
| insert_deprecated("BIND_PCI_REVISION", 0x0105); |
| |
| // usb binding variables at 0x02XX |
| // these are used for both ZX_PROTOCOL_USB and ZX_PROTOCOL_USB_FUNCTION |
| insert_deprecated("BIND_USB_VID", 0x0200); |
| insert_deprecated("BIND_USB_PID", 0x0201); |
| insert_deprecated("BIND_USB_CLASS", 0x0202); |
| insert_deprecated("BIND_USB_SUBCLASS", 0x0203); |
| insert_deprecated("BIND_USB_PROTOCOL", 0x0204); |
| |
| // Platform bus binding variables at 0x03XX |
| insert_deprecated("BIND_PLATFORM_DEV_VID", 0x0300); |
| insert_deprecated("BIND_PLATFORM_DEV_PID", 0x0301); |
| insert_deprecated("BIND_PLATFORM_DEV_DID", 0x0302); |
| insert_deprecated("BIND_PLATFORM_PROTO", 0x0303); |
| |
| // 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. |
| insert_deprecated("BIND_ACPI_HID_0_3", 0x0400); |
| insert_deprecated("BIND_ACPI_HID_4_7", 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. |
| insert_deprecated("BIND_ACPI_CID_0_3", 0x0402); |
| insert_deprecated("BIND_ACPI_CID_4_7", 0x0403); |
| |
| // Intel HDA Codec binding variables at 0x05XX |
| insert_deprecated("BIND_IHDA_CODEC_VID", 0x0500); |
| insert_deprecated("BIND_IHDA_CODEC_DID", 0x0501); |
| insert_deprecated("BIND_IHDA_CODEC_MAJOR_REV", 0x0502); |
| insert_deprecated("BIND_IHDA_CODEC_MINOR_REV", 0x0503); |
| insert_deprecated("BIND_IHDA_CODEC_VENDOR_REV", 0x0504); |
| insert_deprecated("BIND_IHDA_CODEC_VENDOR_STEP", 0x0505); |
| |
| // Serial binding variables at 0x06XX |
| insert_deprecated("BIND_SERIAL_CLASS", 0x0600); |
| insert_deprecated("BIND_SERIAL_VID", 0x0601); |
| insert_deprecated("BIND_SERIAL_PID", 0x0602); |
| |
| // NAND binding variables at 0x07XX |
| insert_deprecated("BIND_NAND_CLASS", 0x0700); |
| |
| // Bluetooth binding variables at 0x08XX |
| insert_deprecated("BIND_BT_GATT_SVC_UUID16", 0x0800); |
| // 128-bit UUID is split across 4 32-bit unsigned ints |
| insert_deprecated("BIND_BT_GATT_SVC_UUID128_1", 0x0801); |
| insert_deprecated("BIND_BT_GATT_SVC_UUID128_2", 0x0802); |
| insert_deprecated("BIND_BT_GATT_SVC_UUID128_3", 0x0803); |
| insert_deprecated("BIND_BT_GATT_SVC_UUID128_4", 0x0804); |
| |
| // SDIO binding variables at 0x09XX |
| insert_deprecated("BIND_SDIO_VID", 0x0900); |
| insert_deprecated("BIND_SDIO_PID", 0x0901); |
| insert_deprecated("BIND_SDIO_FUNCTION", 0x0902); |
| |
| // I2C binding variables at 0x0A0X |
| insert_deprecated("BIND_I2C_CLASS", 0x0A00); |
| insert_deprecated("BIND_I2C_BUS_ID", 0x0A01); |
| insert_deprecated("BIND_I2C_ADDRESS", 0x0A02); |
| |
| // GPIO binding variables at 0x0A1X |
| insert_deprecated("BIND_GPIO_PIN", 0x0A10); |
| |
| // POWER binding variables at 0x0A2X |
| insert_deprecated("BIND_POWER_DOMAIN", 0x0A20); |
| |
| // POWER binding variables at 0x0A3X |
| insert_deprecated("BIND_CLOCK_ID", 0x0A30); |
| |
| // SPI binding variables at 0x0A4X |
| insert_deprecated("BIND_SPI_CLASS", 0x0A40); |
| insert_deprecated("BIND_SPI_BUS_ID", 0x0A41); |
| insert_deprecated("BIND_SPI_CHIP_SELECT", 0x0A42); |
| |
| // 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. |
| insert_deprecated("BIND_TOPO_START", 0x0B00); |
| insert_deprecated("BIND_TOPO_PCI", 0x0B00); |
| insert_deprecated("BIND_TOPO_I2C", 0x0B01); |
| insert_deprecated("BIND_TOPO_SPI", 0x0B02); |
| insert_deprecated("BIND_TOPO_VENDOR_START", 0x0B80); |
| insert_deprecated("BIND_TOPO_VENDOR_END", 0x0BFF); |
| insert_deprecated("BIND_TOPO_END", 0x0BFF); |
| |
| symbol_table |
| } |
| |
| #[derive(Debug, PartialEq)] |
| 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 { |
| 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) |
| } |
| } |
| } |
| } |
| |
| fn compile_statements( |
| statements: Vec<Statement>, |
| symbol_table: &HashMap<CompoundIdentifier, Symbol>, |
| ) -> Result<Vec<SymbolicInstruction>, CompilerError> { |
| let mut compiler = Compiler::new(symbol_table); |
| compiler.compile_statements(statements)?; |
| Ok(compiler.instructions) |
| } |
| |
| struct Compiler<'a> { |
| instructions: Vec<SymbolicInstruction>, |
| next_label_id: u32, |
| symbol_table: &'a HashMap<CompoundIdentifier, Symbol>, |
| } |
| |
| impl<'a> Compiler<'a> { |
| fn new(symbol_table: &'a HashMap<CompoundIdentifier, Symbol>) -> 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))?; |
| 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)), |
| Value::BoolLiteral(b) => Ok(Symbol::BoolValue(b)), |
| Value::Identifier(ident) => self |
| .symbol_table |
| .get(&ident) |
| .ok_or(CompilerError::UnknownKey(ident)) |
| .map(|x| x.clone()), |
| } |
| } |
| |
| fn compile_statements(&mut self, statements: Vec<Statement>) -> Result<(), CompilerError> { |
| self.compile_block(statements)?; |
| |
| // If none of the statements caused an abort, then we should bind the driver. |
| self.instructions.push(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>) -> Result<(), CompilerError> { |
| let mut iter = statements.into_iter().peekable(); |
| while let Some(statement) = iter.next() { |
| match statement { |
| Statement::ConditionStatement(Condition { lhs, op, rhs }) => { |
| let lhs_symbol = self.lookup_identifier(lhs)?; |
| let rhs_symbol = self.lookup_value(rhs)?; |
| let instruction = match op { |
| ConditionOp::Equals => { |
| SymbolicInstruction::AbortIfEqual { lhs: lhs_symbol, rhs: rhs_symbol } |
| } |
| ConditionOp::NotEquals => SymbolicInstruction::AbortIfNotEqual { |
| lhs: lhs_symbol, |
| rhs: rhs_symbol, |
| }, |
| }; |
| self.instructions.push(instruction); |
| } |
| Statement::Accept { identifier, values } => { |
| let lhs_symbol = self.lookup_identifier(identifier)?; |
| let label_id = self.get_unique_label(); |
| for value in values { |
| self.instructions.push(SymbolicInstruction::JumpIfEqual { |
| lhs: lhs_symbol.clone(), |
| rhs: self.lookup_value(value)?, |
| label: label_id, |
| }); |
| } |
| self.instructions.push(SymbolicInstruction::UnconditionalAbort); |
| self.instructions.push(SymbolicInstruction::Label(label_id)); |
| } |
| Statement::If { blocks, else_block } => { |
| if !iter.peek().is_none() { |
| return Err(CompilerError::IfStatementMustBeTerminal); |
| } |
| |
| let final_label_id = self.get_unique_label(); |
| |
| for (Condition { lhs, op, rhs }, block_statements) in blocks { |
| 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(instruction); |
| |
| // Compile the block itself. |
| self.compile_block(block_statements)?; |
| |
| // Jump to after the if statement. |
| self.instructions |
| .push(SymbolicInstruction::UnconditionalJump { label: final_label_id }); |
| |
| // Insert a label to jump to when the condition fails. |
| self.instructions.push(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(SymbolicInstruction::Label(final_label_id)); |
| } |
| Statement::Abort => { |
| self.instructions.push(SymbolicInstruction::UnconditionalAbort); |
| } |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| |
| 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!["deprecated", "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 program = bind_program::Ast { |
| using: vec![], |
| statements: vec![Statement::ConditionStatement(Condition { |
| lhs: make_identifier!("abc"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(42), |
| })], |
| }; |
| 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![ |
| SymbolicInstruction::AbortIfEqual { |
| lhs: Symbol::Key("abc".to_string(), bind_library::ValueType::Number), |
| rhs: Symbol::NumberValue(42) |
| }, |
| SymbolicInstruction::UnconditionalBind |
| ]) |
| ); |
| } |
| |
| #[test] |
| fn accept() { |
| let program = bind_program::Ast { |
| using: vec![], |
| statements: vec![Statement::Accept { |
| 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![ |
| SymbolicInstruction::JumpIfEqual { |
| lhs: Symbol::Key("abc".to_string(), bind_library::ValueType::Number), |
| rhs: Symbol::NumberValue(42), |
| label: 0 |
| }, |
| SymbolicInstruction::JumpIfEqual { |
| lhs: Symbol::Key("abc".to_string(), bind_library::ValueType::Number), |
| rhs: Symbol::NumberValue(314), |
| label: 0 |
| }, |
| SymbolicInstruction::UnconditionalAbort, |
| SymbolicInstruction::Label(0), |
| SymbolicInstruction::UnconditionalBind, |
| ]) |
| ); |
| } |
| |
| #[test] |
| fn if_else() { |
| let program = bind_program::Ast { |
| using: vec![], |
| statements: vec![Statement::If { |
| blocks: vec![ |
| ( |
| Condition { |
| lhs: make_identifier!("abc"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(1), |
| }, |
| vec![Statement::ConditionStatement(Condition { |
| lhs: make_identifier!("abc"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(2), |
| })], |
| ), |
| ( |
| Condition { |
| lhs: make_identifier!("abc"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(2), |
| }, |
| vec![Statement::ConditionStatement(Condition { |
| lhs: make_identifier!("abc"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(3), |
| })], |
| ), |
| ], |
| else_block: vec![Statement::ConditionStatement(Condition { |
| lhs: make_identifier!("abc"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(3), |
| })], |
| }], |
| }; |
| 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![ |
| SymbolicInstruction::JumpIfNotEqual { |
| lhs: Symbol::Key("abc".to_string(), bind_library::ValueType::Number), |
| rhs: Symbol::NumberValue(1), |
| label: 1 |
| }, |
| SymbolicInstruction::AbortIfEqual { |
| lhs: Symbol::Key("abc".to_string(), bind_library::ValueType::Number), |
| rhs: Symbol::NumberValue(2) |
| }, |
| SymbolicInstruction::UnconditionalJump { label: 0 }, |
| SymbolicInstruction::Label(1), |
| SymbolicInstruction::JumpIfNotEqual { |
| lhs: Symbol::Key("abc".to_string(), bind_library::ValueType::Number), |
| rhs: Symbol::NumberValue(2), |
| label: 2 |
| }, |
| SymbolicInstruction::AbortIfEqual { |
| lhs: Symbol::Key("abc".to_string(), bind_library::ValueType::Number), |
| rhs: Symbol::NumberValue(3) |
| }, |
| SymbolicInstruction::UnconditionalJump { label: 0 }, |
| SymbolicInstruction::Label(2), |
| SymbolicInstruction::AbortIfEqual { |
| lhs: Symbol::Key("abc".to_string(), bind_library::ValueType::Number), |
| rhs: Symbol::NumberValue(3) |
| }, |
| SymbolicInstruction::Label(0), |
| SymbolicInstruction::UnconditionalBind, |
| ]) |
| ); |
| } |
| |
| #[test] |
| fn if_else_must_be_terminal() { |
| let program = bind_program::Ast { |
| using: vec![], |
| statements: vec![ |
| Statement::If { |
| blocks: vec![( |
| Condition { |
| lhs: make_identifier!("abc"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(1), |
| }, |
| vec![Statement::ConditionStatement(Condition { |
| lhs: make_identifier!("abc"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(2), |
| })], |
| )], |
| else_block: vec![Statement::ConditionStatement(Condition { |
| lhs: make_identifier!("abc"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(3), |
| })], |
| }, |
| Statement::Accept { |
| 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 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")) |
| )) |
| ); |
| } |
| } |