mod.rs
  1  // Copyright (c) 2025 ADnet Contributors
  2  // This file is part of the AlphaVM 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  }