use crate::libs::pretty_printer;
use crate::{RuntimeErrorKind, set_print_direct};
use crate::utils::{expand_home, is_cfm_mode};
use crate::with_print_direct;
use crate::{Environment, Expression, MAX_RUNTIME_RECURSION, MAX_SYNTAX_RECURSION, SyntaxError};
use crate::{SyntaxErrorKind, parse_script};
use std::collections::HashSet;
use std::fs::{create_dir, read_to_string, write};
use std::io::{self, Write};
use std::path::PathBuf;
use std::rc::Rc;
pub fn run_file(pb: PathBuf, env: &mut Environment) {
match read_to_string(pb.clone()) {
Ok(prelude) => {
env.define(
"SCRIPT",
Expression::String(pb.to_string_lossy().to_string()),
);
let code = parse_and_eval(&prelude, env);
std::process::exit(code as i32);
}
Err(e) => {
eprintln!(
"\x1b[31m[IO ERROR]\x1b[0mFailed to read file '{}':\n {e}",
pb.display()
);
std::process::exit(-1);
}
}
}
pub fn parse_with_mode(input: &str) -> Result<Expression, SyntaxError> {
let cfm = is_cfm_mode(input);
let r = parse(input);
if cfm {
match r {
Ok(Expression::Symbol(s)) => {
if s.chars().any(|c| c.is_control() || c == '\0') {
return Err(SyntaxError {
source: format!("{input}").into(),
kind: SyntaxErrorKind::InvalidCmdSymbol(input.to_string()),
});
}
Ok(Expression::Command(
Rc::new(Expression::Symbol(s)),
Rc::new(vec![]),
))
}
#[cfg(unix)]
Ok(Expression::String(s)) if !s.ends_with("/") && s.contains("/") => {
if s.chars().any(|c| c.is_control() || c == '\0') {
return Err(SyntaxError {
source: format!("{input}").into(),
kind: SyntaxErrorKind::InvalidCmdSymbol(input.to_string()),
});
}
if s.contains("..") && !s.starts_with("../") {
return Err(SyntaxError {
source: format!("{input}").into(),
kind: SyntaxErrorKind::InvalidCmdSymbol(input.to_string()),
});
}
Ok(Expression::Command(
Rc::new(Expression::Symbol(s)),
Rc::new(vec![]),
))
}
#[cfg(windows)]
Ok(Expression::String(s))
if (s.contains(":\\")
|| s.contains(".\\")
|| s.contains(":/")
|| s.contains("./")) =>
{
if s.chars().any(|c| c.is_control() || c == '\0') {
return Err(SyntaxError {
source: format!("{input}").into(),
kind: SyntaxErrorKind::InvalidCmdSymbol(input.to_string()),
});
}
if (s.contains(":\\") || s.contains(":/")) && s.len() < 3 {
return Err(SyntaxError {
source: format!("{input}").into(),
kind: SyntaxErrorKind::InvalidCmdSymbol(input.to_string()),
});
}
Ok(Expression::Command(
Rc::new(Expression::Symbol(s)),
Rc::new(vec![]),
))
}
other => other,
}
} else {
r
}
}
pub fn parse(input: &str) -> Result<Expression, SyntaxError> {
match parse_script(input) {
Ok(result) => Ok(result),
Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => Err(SyntaxError {
source: format!("{input} ").into(),
kind: e,
}),
Err(nom::Err::Incomplete(_)) => Err(SyntaxError {
source: input.into(),
kind: SyntaxErrorKind::InternalError("incomplted".to_string()),
}),
}
}
pub fn check(input: &str) -> bool {
parse_script(input).is_ok()
}
pub fn knock_validate(env: &mut Environment) {
if let Some(validator) = env.get("LUME_KNOCK_VALIDATOR") {
match validator {
Expression::Function(..) | Expression::Lambda(..) => {
let r = validator.apply(vec![]).eval_cmd(env);
if let Ok(Expression::Boolean(false)) = r {
std::process::exit(1);
}
}
_ => {}
};
}
}
pub fn parse_and_eval(text: &str, env: &mut Environment) -> u8 {
if text.is_empty() {
return 0;
};
let parsed = parse_with_mode(text);
match parsed {
Ok(expr) => {
let val = expr.eval_cmd(env);
match val {
Ok(Expression::None) => {}
Ok(m)
if matches!(
m,
Expression::Map(_)
| Expression::HMap(_)
| Expression::List(_)
| Expression::Table(_)
) =>
{
let _ = pretty_printer(&m);
}
Ok(result) => {
if with_print_direct(|v| v) {
match result {
Expression::Function(..) => {}
Expression::Lambda(..) => {}
r => println!("\n >> [{}] <<\n{}", r.type_name(), r),
};
}
}
Err(e) if let RuntimeErrorKind::Exited(code) = e.kind => {
if code == 0 {
println!("exited");
} else {
eprintln!("exited with code {code}");
}
return code; }
Err(e) => {
let _ = io::stdout().flush();
eprintln!("\x1b[31m_____________\x1b[0m\n{e}");
return e.code();
}
}
return 0;
}
Err(e) => {
let _ = io::stdout().flush();
eprintln!("\x1b[31m[PARSE ERROR]\x1b[0m\n{e}");
return e.code();
}
}
}
pub fn init_config(env: &mut Environment) {
let profile = match env.get("LUME_PROFILE") {
Some(p) => PathBuf::from(p.to_string()),
_ => match dirs::config_dir() {
Some(config_dir) => {
let config_path = config_dir.join("lumesh");
if !config_path.exists()
&& let Err(e) = create_dir(&config_path)
{
eprintln!("Error while create prelude dir: {e}");
}
config_path.join("config.lm")
}
_ => PathBuf::from(".lume_config"),
},
};
if !profile.exists() {
let prompt = format!(
"Could not find profile file at: {}\nWould you like me to write a default one? (Y/n)\n>>> ",
profile.display()
);
let response = read_user_input(prompt);
const INTRO_PRELUDE: &str = if cfg!(target_os = "macos") {
include_str!("config/config_mac.lm")
} else if cfg!(windows) {
include_str!("config/config_win.lm")
} else {
include_str!("config/config.lm")
};
if response.is_empty() || response.to_lowercase() == "y" {
if let Err(e) = write(&profile, INTRO_PRELUDE) {
eprintln!("Error while writing prelude: {e}");
}
env.define(
"SCRIPT",
Expression::String(profile.to_string_lossy().to_string()),
);
}
if parse_and_eval(INTRO_PRELUDE, env) > 0 {
eprintln!("Sorry, the config seems has some issue");
}
} else {
match read_to_string(profile.clone()) {
Ok(prelude) => {
env.define(
"SCRIPT",
Expression::String(profile.to_string_lossy().to_string()),
);
let _ = parse_and_eval(&prelude, env);
}
Err(_) => {
eprintln!("Failed to load config");
}
}
}
if let Some(pd) = env.get("LUME_PRINT_DIRECT") {
set_print_direct(pd.is_truthy());
}
if let Some(Expression::Integer(run_rec)) = env.get("LUME_MAX_RUNTIME_RECURSION") {
MAX_RUNTIME_RECURSION.with_borrow_mut(|v| *v = run_rec as usize)
}
if let Some(Expression::Integer(run_rec)) = env.get("LUME_MAX_SYNTAX_RECURSION") {
MAX_SYNTAX_RECURSION.with_borrow_mut(|v| *v = run_rec as usize)
}
env.undefine("SCRIPT");
init_cmds(env);
}
fn init_cmds(env: &mut Environment) {
if !env.has("IFS") {
env.define("IFS", Expression::None);
}
if !env.has("LUME_IFS_MODE") {
env.define("IFS", Expression::Integer(60));
}
#[cfg(unix)]
let sp = ":";
#[cfg(windows)]
let sp = ";";
if let Some(Expression::String(pathes)) = env.get("PATH") {
let np = pathes
.split_terminator(sp)
.filter(|p| !p.is_empty()) .map(|p| expand_home(p))
.collect::<HashSet<_>>() .into_iter()
.collect::<Vec<_>>()
.join(sp);
unsafe {
std::env::set_var("PATH", np.clone());
}
env.define_in_root("PATH", Expression::String(np));
} else {
#[cfg(unix)]
env.define_in_root(
"PATH",
Expression::String("/usr/local/bin:/usr/sbin:/usr/bin:".to_owned()),
);
#[cfg(windows)]
env.define_in_root(
"PATH",
Expression::String("C:\\windows\\system32;C:\\windows\\;".to_owned()),
);
}
}
pub fn read_user_input(prompt: impl ToString) -> String {
print!("{}", prompt.to_string());
let _ = io::stdout().flush();
let mut input = String::new();
let _ = io::stdin()
.read_line(&mut input)
.map_err(|e| eprintln!("Read Failed: {e}"));
input.trim().to_owned()
}
pub const IFS_CMD: u8 = 1 << 1; pub const IFS_FOR: u8 = 1 << 2; pub const IFS_STR: u8 = 1 << 3; pub const IFS_CSV: u8 = 1 << 4; pub const IFS_PCK: u8 = 1 << 5; pub fn ifs_contains(mode: u8, env: &mut Environment) -> bool {
if let Some(Expression::Integer(m)) = env.get("LUME_IFS_MODE")
&& m as u8 & mode != 0
{
return true;
}
false
}