use std::fs;
use std::iter::Peekable;
use std::path::Path;
use std::vec::IntoIter;
#[derive(Clone)]
pub struct ArgumentPair {
pub(crate) first: String,
pub(crate) second: u128,
}
#[allow(non_camel_case_types)]
#[derive(Clone)]
pub enum CommandType {
C_ARITHMETIC(String),
C_PUSH(ArgumentPair),
C_LABEL(String),
C_POP(ArgumentPair),
C_GOTO(String),
C_IF(String),
C_FUNCTION(ArgumentPair),
C_RETURN,
C_CALL(ArgumentPair),
}
pub struct Parser {
vm_source_code: Box<Peekable<IntoIter<String>>>, pub current_command: String,
}
impl Parser {
pub fn new(filepath: &Path) -> Parser {
println!("Reading vm file: {}", filepath.display());
Parser {
vm_source_code: Box::new(
fs::read_to_string(filepath)
.expect("Couldn't read the source code")
.lines()
.map(|x| x.to_string())
.collect::<Vec<String>>()
.into_iter()
.peekable(),
),
current_command: "".to_owned(),
}
}
fn clean_line(line: String) -> String {
let mut line = line.trim();
if line.contains("//") {
line = line.split_once("//").unwrap().0;
line = line.trim();
};
line.to_string()
}
pub fn has_more_lines(&mut self) -> bool {
self.vm_source_code.peek().is_some()
}
pub fn advance(&mut self) {
if !self.has_more_lines() {
panic!("Can't advance since no more vm code to interprete.")
};
self.current_command = Self::clean_line(self.vm_source_code.next().unwrap());
if self.current_command.is_empty() {
self.advance();
}
}
pub fn command_type(&self) -> CommandType {
let mut command_list = self.current_command.split_ascii_whitespace();
let command = command_list
.next()
.expect("Command section of current instruction couldn't be extracted");
match command {
"and" | "or" | "sub" | "eq" | "neg" | "gt" | "lt" | "not" | "add" => {
CommandType::C_ARITHMETIC(command.to_string())
}
"push" => CommandType::C_PUSH(ArgumentPair {
first: command_list
.next()
.expect("First argument of push is expected!")
.to_owned(),
second: command_list
.next()
.expect("Second argument of push is expected!")
.parse()
.unwrap(),
}),
"pop" => CommandType::C_POP(ArgumentPair {
first: command_list
.next()
.expect("First argument of pop is expected!")
.to_string(),
second: command_list
.next()
.expect("Second argument of pop is expected!")
.parse()
.unwrap(),
}),
"label" => CommandType::C_LABEL(String::from(
command_list
.next()
.expect("A label location: string required"),
)),
"goto" => CommandType::C_GOTO(String::from(
command_list
.next()
.expect("Label location required for goto"),
)),
"if-goto" => CommandType::C_IF(String::from(
command_list
.next()
.expect("Label location required for if-goto"),
)),
"function" => CommandType::C_FUNCTION(ArgumentPair {
first: command_list
.next()
.expect("The function name has to be passed")
.to_string(),
second: command_list
.next()
.expect("n_vars; number of local variables required")
.parse()
.unwrap(),
}),
"return" => CommandType::C_RETURN,
"call" => CommandType::C_CALL(ArgumentPair {
first: command_list
.next()
.expect("function name not passed")
.to_string(),
second: command_list
.next()
.expect("nArgs: number of arguments pushed ahead required")
.parse()
.unwrap(),
}),
_ => panic!(
"Couldn't decipher instruction type {}",
self.current_command
),
}
}
pub fn arg1(&self) -> String {
match self.command_type() {
CommandType::C_RETURN => panic!("Arg 1 can't be called with RETURN type"),
CommandType::C_ARITHMETIC(command) => command.to_lowercase(),
CommandType::C_PUSH(argument_pair) => argument_pair.first.to_lowercase(),
CommandType::C_POP(argument_pair) => argument_pair.first.to_lowercase(),
CommandType::C_LABEL(command) => command, CommandType::C_GOTO(command) => command, CommandType::C_IF(command) => command, CommandType::C_FUNCTION(argument_pair) => argument_pair.first,
CommandType::C_CALL(argument_pair) => argument_pair.first,
}
}
pub fn arg2(&self) -> u128 {
match self.command_type() {
CommandType::C_PUSH(argument_pair) | CommandType::C_POP(argument_pair) => {
argument_pair.second
}
CommandType::C_FUNCTION(argument_pair) => argument_pair.second,
CommandType::C_CALL(argument_pair) => argument_pair.second,
_ => panic!("Only call for C_PUSH, C_POP, C_CALL, C_FUNCTION commandType"),
}
}
}
#[cfg(test)]
mod tests {
#[test]
#[should_panic(expected = "Can't advance since no more vm code to interprete.")]
fn testing_advance_when_no_line() {
todo!("Impl to test when we try to advance an empty or completed file")
}
}