use std::{env, fs, io::Write, process::Command};
use colored::Colorize;
#[derive(Debug, PartialEq)]
pub enum TokenKind {
Word,
Comp,
Args,
Symbol,
StringLiteral,
}
#[derive(Debug)]
pub struct Token {
pub kind: TokenKind,
pub lexeme: String,
}
impl Token {
pub fn new(lexeme: String) -> Token {
match lexeme.as_str() {
"COMP" => Token { kind: TokenKind::Comp, lexeme },
"COMPILER" => Token { kind: TokenKind::Comp, lexeme },
"ARGS" => Token { kind: TokenKind::Args, lexeme },
"ARGUMENTS" => Token { kind: TokenKind::Args, lexeme },
_ => Token { kind: TokenKind::Word, lexeme },
}
}
}
pub struct UnifyAllSetup {
compiler: String,
args: Vec<String>,
}
fn parse_unifyall_comp_section(tokens: &[Token], index: &mut usize) -> String {
if *index + 2 < tokens.len() {
if tokens[*index + 1].lexeme == ":" && tokens[*index + 2].kind == TokenKind::StringLiteral {
*index += 2;
return tokens[*index].lexeme.clone();
} else {
panic!(
"[{}]: {}\n",
"UnifyAll".magenta().bold(),
"Expected ':' followed by a string literal".red().bold()
);
}
} else {
panic!(
"[{}]: {}",
"UnifyAll".magenta().bold(),
"Unexpected end of file; expected ':' followed by a string literal".red().bold()
);
}
}
fn parse_unifyall_args_section(tokens: &[Token], index: &mut usize) -> Vec<String> {
let mut args = Vec::new();
if *index + 2 < tokens.len() {
if tokens[*index + 1].lexeme == ":" && tokens[*index + 2].kind == TokenKind::StringLiteral {
*index += 2;
loop {
if *index >= tokens.len() {
break;
}
if tokens[*index].kind == TokenKind::StringLiteral {
args.push(tokens[*index].lexeme.clone());
*index += 1;
if *index < tokens.len() && tokens[*index].lexeme == "," {
*index += 1;
}
} else {
break;
}
}
} else {
panic!(
"[{}]: {}",
"UnifyAll".magenta().bold(),
"Expected ':' followed by at least one string literal for ARGS".red().bold()
);
}
} else {
panic!(
"[{}]: {}",
"UnifyAll".magenta().bold(),
"Unexpected end of file; expected ':' followed by a string literal".red().bold()
);
}
args
}
impl UnifyAllSetup {
pub fn new() -> Self {
Self { compiler: String::new(), args: Vec::new() }
}
pub fn parse(content: String) -> Self {
let chars: Vec<char> = content.chars().collect();
let mut tokens = Vec::new();
let mut index = 0;
while index < chars.len() {
match chars[index] {
'#' => {
while index < chars.len() && chars[index] != '\n' {
index += 1;
}
}
c if c.is_alphabetic() => {
let start = index;
while index < chars.len() && chars[index].is_alphabetic() {
index += 1;
}
let word: String = chars[start..index].iter().collect();
tokens.push(Token::new(word));
continue;
}
'\"' => {
index += 1;
if index < chars.len() && chars[index] == '\"' {
tokens.push(Token { kind: TokenKind::StringLiteral, lexeme: String::new() });
index += 1;
continue;
}
let mut str_lit = String::new();
while index < chars.len() && chars[index] != '\"' {
if chars[index] == '\n' {
panic!(
"[{}]: {}",
"UnifyAll".magenta().bold(),
"Unterminated string literal".red().bold()
);
}
str_lit.push(chars[index]);
index += 1;
}
if index >= chars.len() || chars[index] != '\"' {
panic!(
"[{}]: {}",
"UnifyAll".magenta().bold(),
"Unterminated string literal".red().bold()
);
}
tokens.push(Token { kind: TokenKind::StringLiteral, lexeme: str_lit });
}
c if c.is_whitespace() => {
}
c => {
tokens.push(Token { kind: TokenKind::Symbol, lexeme: c.to_string() });
}
}
index += 1;
}
let mut setup = UnifyAllSetup::new();
let mut token_index = 0;
while token_index < tokens.len() {
match tokens[token_index].kind {
TokenKind::Comp => {
setup.compiler = parse_unifyall_comp_section(&tokens, &mut token_index);
}
TokenKind::Args => {
setup.args = parse_unifyall_args_section(&tokens, &mut token_index);
}
_ => {
panic!(
"[{}]: {} {}\n",
"UnifyAll".magenta().bold(),
"Unexpected token:".red().bold(),
format!("{:?}", tokens[token_index]).red().bold()
);
}
}
token_index += 1;
}
setup
}
pub fn compiler(&self) -> &String {
&self.compiler
}
pub fn args(&self) -> &Vec<String> {
&self.args
}
}
fn create_unify_all_file_content(args: &Vec<String>) -> String {
let mut file_content = String::from("# Build script\n\nCOMP: \"");
file_content.push_str(&args[2]);
file_content.push_str("\"\n\nARGS:\n");
for i in 3..args.len() {
file_content.push_str(" \"");
file_content.push_str(&args[i]);
file_content.push_str("\",\n");
}
file_content
}
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
panic!(
"[{}]: {}\nUsage: {} <path to .u file>\n{}: Additional arguments must follow the correct order for the compiler.\n",
"UnifyAll".magenta().bold(),
"Missing file path argument".red().bold(),
"UnifyAll".magenta().bold(),
"Note".blue().bold()
);
}
if args[1] == "--new" || args[1] == "--n" {
let mut unify_all_file = fs::File::create("build.u").unwrap();
let content = create_unify_all_file_content(&args);
unify_all_file.write_all(content.as_bytes()).unwrap();
println!("[{}] {}", "UnifyAll".magenta().bold(), "Successfully created 'build.u'\n".green().bold());
} else {
let contents = fs::read_to_string(&args[1]).unwrap();
let us = UnifyAllSetup::parse(contents);
print!(
"[{}] {}\nCompiler: {}\nArguments: {}\nCommand-line output: {}",
"UnifyAll".magenta().bold(),
"Config".magenta().bold(),
us.compiler().yellow().bold(),
us.args().len().to_string().green().bold(),
us.compiler().yellow()
);
for arg in us.args() {
print!(" {}", arg)
}
println!("\n");
let output = Command::new(us.compiler())
.args(us.args())
.output()
.expect(&format!(
"[{}]: {}",
"UnifyAll".magenta().bold(),
"Failed to execute command".red().bold()
));
if output.status.success() {
println!("[{}] {}\n", "UnifyAll".magenta().bold(), "Finished building".green().bold());
} else {
eprintln!(
"[{}]: {}\n{}",
"UnifyAll".magenta().bold(),
"Building failed".red().bold(),
String::from_utf8_lossy(&output.stderr)
);
}
}
}