mod.rs
1 // Copyright (c) 2019-2025 Alpha-Delta Network Inc. 2 // This file is part of the deltavm library. 3 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at: 7 8 // http://www.apache.org/licenses/LICENSE-2.0 9 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 mod bytes; 17 mod parse; 18 19 use crate::Command; 20 21 use console::{ 22 network::prelude::*, 23 program::{Identifier, Register}, 24 }; 25 26 use std::collections::HashMap; 27 28 #[derive(Clone, PartialEq, Eq)] 29 pub struct ConstructorCore<N: Network> { 30 /// The commands, in order of execution. 31 commands: Vec<Command<N>>, 32 /// The number of write commands. 33 num_writes: u16, 34 /// A mapping from `Position`s to their index in `commands`. 35 positions: HashMap<Identifier<N>, usize>, 36 } 37 38 impl<N: Network> ConstructorCore<N> { 39 /// Returns the constructor commands. 40 pub fn commands(&self) -> &[Command<N>] { 41 &self.commands 42 } 43 44 /// Returns the number of write commands. 45 pub const fn num_writes(&self) -> u16 { 46 self.num_writes 47 } 48 49 /// Returns the mapping of `Position`s to their index in `commands`. 50 pub const fn positions(&self) -> &HashMap<Identifier<N>, usize> { 51 &self.positions 52 } 53 54 /// Returns `true` if the constructor commands contain a string type. 55 pub fn contains_string_type(&self) -> bool { 56 self.commands.iter().any(|command| command.contains_string_type()) 57 } 58 59 /// Returns `true` if the constructor contains an array type with a size that exceeds the given maximum. 60 pub fn exceeds_max_array_size(&self, max_array_size: u32) -> bool { 61 self.commands.iter().any(|command| command.exceeds_max_array_size(max_array_size)) 62 } 63 } 64 65 impl<N: Network> ConstructorCore<N> { 66 /// Adds the given command to constructor. 67 /// 68 /// # Errors 69 /// This method will halt if the maximum number of commands has been reached. 70 #[inline] 71 pub fn add_command(&mut self, command: Command<N>) -> Result<()> { 72 // Ensure the maximum number of commands has not been exceeded. 73 ensure!(self.commands.len() < N::MAX_COMMANDS, "Cannot add more than {} commands", N::MAX_COMMANDS); 74 // Ensure the number of write commands has not been exceeded. 75 if command.is_write() { 76 ensure!( 77 self.num_writes < N::MAX_WRITES, 78 "Cannot add more than {} 'set' & 'remove' commands", 79 N::MAX_WRITES 80 ); 81 } 82 83 // Ensure the command is not an async instruction. 84 ensure!(!command.is_async(), "Forbidden operation: Constructor cannot invoke an 'async' instruction"); 85 // Ensure the command is not a call instruction. 86 ensure!(!command.is_call(), "Forbidden operation: Constructor cannot invoke a 'call'"); 87 // Ensure the command is not a cast to record instruction. 88 ensure!(!command.is_cast_to_record(), "Forbidden operation: Constructor cannot cast to a record"); 89 // Ensure the command is not an await command. 90 ensure!(!command.is_await(), "Forbidden operation: Constructor cannot 'await'"); 91 92 // Check the destination registers. 93 for register in command.destinations() { 94 // Ensure the destination register is a locator. 95 ensure!(matches!(register, Register::Locator(..)), "Destination register must be a locator"); 96 } 97 98 // Check if the command is a branch command. 99 if let Some(position) = command.branch_to() { 100 // Ensure the branch target does not reference an earlier position. 101 ensure!(!self.positions.contains_key(position), "Cannot branch to an earlier position '{position}'"); 102 } 103 104 // Check if the command is a position command. 105 if let Some(position) = command.position() { 106 // Ensure the position is not yet defined. 107 ensure!(!self.positions.contains_key(position), "Cannot redefine position '{position}'"); 108 // Ensure that there are less than `u8::MAX` positions. 109 ensure!(self.positions.len() < N::MAX_POSITIONS, "Cannot add more than {} positions", N::MAX_POSITIONS); 110 // Insert the position. 111 self.positions.insert(*position, self.commands.len()); 112 } 113 114 // Check if the command is a write command. 115 if command.is_write() { 116 // Increment the number of write commands. 117 self.num_writes += 1; 118 } 119 120 // Insert the command. 121 self.commands.push(command); 122 Ok(()) 123 } 124 } 125 126 impl<N: Network> TypeName for ConstructorCore<N> { 127 /// Returns the type name as a string. 128 #[inline] 129 fn type_name() -> &'static str { 130 "constructor" 131 } 132 } 133 134 #[cfg(test)] 135 mod tests { 136 use super::*; 137 138 use crate::{Command, Constructor}; 139 140 type CurrentNetwork = console::network::MainnetV0; 141 142 #[test] 143 fn test_add_command() { 144 // Initialize a new constructor instance. 145 let mut constructor = Constructor::<CurrentNetwork> { 146 commands: Default::default(), 147 num_writes: 0, 148 positions: Default::default(), 149 }; 150 151 // Ensure that a command can be added. 152 let command = Command::<CurrentNetwork>::from_str("add r0 r1 into r2;").unwrap(); 153 assert!(constructor.add_command(command).is_ok()); 154 155 // Ensure that adding more than the maximum number of commands will fail. 156 for i in 3..CurrentNetwork::MAX_COMMANDS * 2 { 157 let command = Command::<CurrentNetwork>::from_str(&format!("add r0 r1 into r{i};")).unwrap(); 158 159 match constructor.commands.len() < CurrentNetwork::MAX_COMMANDS { 160 true => assert!(constructor.add_command(command).is_ok()), 161 false => assert!(constructor.add_command(command).is_err()), 162 } 163 } 164 165 // Ensure that adding more than the maximum number of writes will fail. 166 167 // Initialize a new constructor instance. 168 let mut constructor = Constructor::<CurrentNetwork> { 169 commands: Default::default(), 170 num_writes: 0, 171 positions: Default::default(), 172 }; 173 174 for _ in 0..CurrentNetwork::MAX_WRITES * 2 { 175 let command = Command::<CurrentNetwork>::from_str("remove object[r0];").unwrap(); 176 177 match constructor.commands.len() < CurrentNetwork::MAX_WRITES as usize { 178 true => assert!(constructor.add_command(command).is_ok()), 179 false => assert!(constructor.add_command(command).is_err()), 180 } 181 } 182 } 183 184 #[test] 185 fn test_add_command_duplicate_positions() { 186 // Initialize a new constructor instance. 187 let mut constructor = 188 Constructor { commands: Default::default(), num_writes: 0, positions: Default::default() }; 189 190 // Ensure that a command can be added. 191 let command = Command::<CurrentNetwork>::from_str("position start;").unwrap(); 192 assert!(constructor.add_command(command.clone()).is_ok()); 193 194 // Ensure that adding a duplicate position will fail. 195 assert!(constructor.add_command(command).is_err()); 196 197 // Helper method to convert a number to a unique string. 198 #[allow(clippy::cast_possible_truncation)] 199 fn to_unique_string(mut n: usize) -> String { 200 let mut s = String::new(); 201 while n > 0 { 202 s.push((b'A' + (n % 26) as u8) as char); 203 n /= 26; 204 } 205 s.chars().rev().collect::<String>() 206 } 207 208 // Ensure that adding more than the maximum number of positions will fail. 209 for i in 1..u8::MAX as usize * 2 { 210 let position = to_unique_string(i); 211 println!("position: {position}"); 212 let command = Command::<CurrentNetwork>::from_str(&format!("position {position};")).unwrap(); 213 214 match constructor.commands.len() < u8::MAX as usize { 215 true => assert!(constructor.add_command(command).is_ok()), 216 false => assert!(constructor.add_command(command).is_err()), 217 } 218 } 219 } 220 }