use std::collections::HashMap;
use std::fs;
use std::process::Command;
// print only with debug_assertions
macro_rules! dprint {
($($x:tt)*) => {
#[cfg(debug_assertions)]
print!($($x)*)
}
}
// // println only with debug_assertions
// macro_rules! dprintln {
// ($($x:tt)*) => {
// #[cfg(debug_assertions)]
// println!($($x)*)
// }
// }
// Point to one or more ranges of a string, useful for highlighting parts of string
macro_rules! highlight_debug {
($hi_col:expr, $str:expr $(, ($pos:tt -> $endpos:tt))*) => {
for (i, _c) in $str.char_indices() {
if false $(|| (i >= $pos) && (i < $endpos))* {
dprint!("{}{}\x1b[0m", $hi_col, _c);
} else {
dprint!("{}", _c);
}
}
dprint!("\n");
};
($str:expr, $pos:expr, $endpos:expr) => {
highlight_debug!("\x1b[7m", $str, ($pos -> $endpos))
};
($str:expr, $pos:expr) => {
highlight_debug!($str, $pos, $pos+1)
};
}
pub enum MacroType {
Function(fn(smp: &mut MacroProcessor, macro_name: &str, args: &mut [String]) -> String),
String(String)
}
pub struct MacroProcessor {
macros: HashMap<String, MacroType>,
}
fn smp_builtin_define(smp: &mut MacroProcessor, macro_name: &str, args: &mut [String]) -> String {
if args.len() < 1 {
return macro_name.to_string();
}
let arg0 = smp.process_input(&args[0]);
if args.len() > 1 {
let arg1 = smp.process_input(&args[1]);
smp.define_macro(arg0, arg1);
} else {
smp.define_macro(arg0, String::new());
}
String::new()
}
fn smp_builtin_ifdef(smp: &mut MacroProcessor, macro_name: &str, args: &mut [String]) -> String {
if args.len() < 2 {
return macro_name.to_string();
}
// We need to expand the first argument here as well, but we need to make the parser
// support literal, and phrase strings
if smp.macros.contains_key(&args[0]) {
return smp.process_input(&args[1]);
}
if args.len() > 2 {
return smp.process_input(&args[2]);
}
String::new()
}
fn smp_builtin_ifndef(smp: &mut MacroProcessor, macro_name: &str, args: &mut [String]) -> String {
if args.len() < 2 {
return macro_name.to_string();
}
// We need to expand the first argument here as well, but we need to make the parser
// support literal, and phrase strings
if !smp.macros.contains_key(&args[0]) {
return smp.process_input(&args[1]);
}
if args.len() > 2 {
return smp.process_input(&args[2]);
}
String::new()
}
fn smp_builtin_ifeq(smp: &mut MacroProcessor, macro_name: &str, args: &mut [String]) -> String {
if args.len() < 3 {
return macro_name.to_string();
}
let arg0 = smp.process_input(&args[0]);
let arg1 = smp.process_input(&args[1]);
if arg0 == arg1 {
return smp.process_input(&args[2]);
}
if args.len() > 3 {
return smp.process_input(&args[3]);
}
String::new()
}
fn smp_builtin_ifneq(smp: &mut MacroProcessor, macro_name: &str, args: &mut [String]) -> String {
if args.len() < 3 {
return macro_name.to_string();
}
let arg0 = smp.process_input(&args[0]);
let arg1 = smp.process_input(&args[1]);
if arg0 != arg1 {
return smp.process_input(&args[2]);
}
if args.len() > 3 {
return smp.process_input(&args[3]);
}
return String::new();
}
fn smp_builtin_include(smp: &mut MacroProcessor, macro_name: &str, args: &mut [String]) -> String {
if args.len() < 1 {
return macro_name.to_string();
}
let arg0 = smp.process_input(&args[0]);
let input_file = fs::read_to_string(&arg0).expect("Failed to read input file");
return smp.process_input(&input_file);
}
fn smp_builtin_shell(smp: &mut MacroProcessor, macro_name: &str, args: &mut [String]) -> String {
if args.len() < 1 {
return macro_name.to_string();
}
let arg0 = smp.process_input(&args[0]);
let res = Command::new("sh")
.arg("-c")
.arg(arg0)
.output();
match res {
Ok(output) => String::from_utf8(output.stdout).expect("SMP1"),
Err(_) => String::new()
}
}
/// Would like one that is better than this tbh
fn smp_builtin_expr(smp: &mut MacroProcessor, macro_name: &str, args: &mut [String]) -> String {
if args.len() < 1 {
return macro_name.to_string();
}
for arg in args.iter_mut() {
*arg = smp.process_input(&arg);
}
let res = Command::new("expr")
.args(args)
.output();
match res {
Ok(output) => String::from_utf8(output.stdout).expect("SMP1"),
Err(_) => String::new()
}
}
impl MacroProcessor {
pub fn new() -> Self {
let mut smp = Self {
macros: HashMap::new(),
};
smp.define_builtins();
smp
}
fn define_builtins(&mut self) {
self.define_macro_fn(String::from("define"), MacroType::Function(smp_builtin_define));
self.define_macro_fn(String::from("ifdef"), MacroType::Function(smp_builtin_ifdef));
self.define_macro_fn(String::from("ifndef"), MacroType::Function(smp_builtin_ifndef));
self.define_macro_fn(String::from("ifeq"), MacroType::Function(smp_builtin_ifeq));
self.define_macro_fn(String::from("ifneq"), MacroType::Function(smp_builtin_ifneq));
self.define_macro_fn(String::from("include"), MacroType::Function(smp_builtin_include));
self.define_macro_fn(String::from("shell"), MacroType::Function(smp_builtin_shell));
self.define_macro_fn(String::from("expr"), MacroType::Function(smp_builtin_expr));
// TODO
// format('Result id %d', 3282)
}
pub fn define_macro(&mut self, name: String, body: String) {
self.macros.insert(name, MacroType::String(body));
}
pub fn define_macro_fn(&mut self, name: String, macro_expansion: MacroType) {
self.macros.insert(name, macro_expansion);
}
/// This expands a macro definition, and it executes builtin functions, like define
fn expand_macro(&mut self, macro_name: &str, args: &mut [String]) -> String {
let Some(macro_body) = self.macros.get(macro_name) else {
return format!("{}", macro_name);
};
match macro_body {
MacroType::String(body) => {
let mut expanded = body.clone();
for (i, arg) in args.iter().enumerate() {
let placeholder = format!("${}", i + 1);
expanded = expanded.replace(&placeholder, arg);
}
return expanded;
},
MacroType::Function(func) => {
return func(self, macro_name, args);
},
}
}
pub fn process_input(&mut self, input: &str) -> String {
let mut output = String::new();
let mut state = ParserState::Normal;
let mut macro_name = String::new();
let mut macro_args = Vec::new();
let mut argument = String::new();
let mut macro_name_start = 0;
let mut skip_next_line_ending = false;
let mut in_quote_single = false;
let mut in_quote_double = false;
for (i, c) in input.char_indices() {
highlight_debug!(input, macro_name_start, i);
match state {
ParserState::Normal => {
macro_name_start = i;
if skip_next_line_ending && (c == '\n') {
skip_next_line_ending = false;
continue;
}
if c.is_alphanumeric() {
state = ParserState::InMacro;
macro_name.push(c);
} else {
output.push(c);
}
}
ParserState::InMacro => {
if c.is_alphanumeric() || c == '_' {
macro_name.push(c);
} else if c == '(' {
state = ParserState::InMacroArgs;
} else {
if self.macros.contains_key(¯o_name) {
highlight_debug!("\x1b[32m\x1b[7m", input, (macro_name_start -> i));
}
if macro_name == "SNNL" {
skip_next_line_ending = c != '\n';
} else {
let expanded = self.expand_macro(¯o_name, &mut []);
output.push_str(&expanded);
output.push(c);
}
macro_name.clear();
state = ParserState::Normal;
}
}
ParserState::InMacroArgs => {
if c == ')' {
highlight_debug!("\x1b[32m\x1b[7m", input, (macro_name_start -> i));
macro_args.push(argument.trim().to_string());
let expanded = self.expand_macro(¯o_name, &mut macro_args);
output.push_str(&expanded);
state = ParserState::Normal;
macro_name.clear();
macro_args.clear();
argument.clear();
} else if c == ',' {
macro_args.push(argument.trim().to_string());
argument.clear();
} else {
argument.push(c);
}
}
}
}
// Handle cases where the text ends with a macro without arguments
if !macro_name.is_empty() {
output.push_str(&self.expand_macro(¯o_name, &mut []));
}
output
}
}
#[derive(Debug, PartialEq)]
enum ParserState {
Normal,
InMacro,
InMacroArgs,
}