use std::{ fs::File, io::{ BufReader, IsTerminal, stdin, stdout, stderr }, path::PathBuf };
use clap::Parser;
use supports_color::Stream;
mod language;
mod runtime;
use crate::{ language::{ interpreter::{ Interpreter, Startup, Interactive, RcFile },
text::{ buffer::{ Buffer, SimpleBuffer }, read_buffer::ReadBuffer } },
runtime::{ color::TtyColorMode,
repl::Repl,
result::{ RuntimeResult, RuntimeError } } };
#[derive(Parser, Debug)]
#[command(version, about = "The Shelly interactive shell and scripting language.")]
struct CommandArguments
{
#[arg(short = 'i', conflicts_with_all = ["script", "stdin_code"])]
interactive: bool,
#[arg(short = 'l', long)]
login: bool,
#[arg(short = 'c', long,
value_name = "code",
conflicts_with_all = ["stdin_code", "interactive"])]
code: Option<String>,
#[arg(short = 's', long = "stdin")]
stdin_code: bool,
#[arg(short = 'b', long)]
no_banner: bool,
#[arg(short = 'm', long = "mono")]
mono: bool,
#[arg(short = 't', long)]
tab_width: Option<usize>,
#[arg(long)]
norc: bool,
#[arg(long, conflicts_with = "norc")]
rcfile: Option<PathBuf>,
#[arg(value_name = "SCRIPT", conflicts_with_all = ["code", "stdin_code"])]
script: Option<PathBuf>,
#[arg(value_name = "script arguments", last = true)]
script_arguments: Vec<String>,
}
enum RunningMode
{
Interactive,
Script,
Code,
Stdin
}
fn determine_running_mode(args: &CommandArguments) -> RunningMode
{
if args.interactive
{
return RunningMode::Interactive;
}
if args.stdin_code
{
return RunningMode::Stdin;
}
if args.code.is_some()
{
return RunningMode::Code;
}
if args.script.is_some()
{
return RunningMode::Script;
}
if stdin().is_terminal()
&& stdout().is_terminal()
&& stderr().is_terminal()
{
return RunningMode::Interactive;
}
RunningMode::Stdin
}
fn determine_color_mode(args: &CommandArguments) -> TtyColorMode
{
if args.mono
{
return TtyColorMode::TtyMonochrome;
}
match supports_color::on(Stream::Stdout)
{
Some(c) if c.has_16m => TtyColorMode::TtyTrueColor,
Some(c) if c.has_256 => TtyColorMode::Tty256,
Some(c) if c.has_basic => TtyColorMode::TtyBasic,
_ => TtyColorMode::TtyMonochrome
}
}
fn run_as_repl(tab_width: usize,
color_mode: TtyColorMode,
startup: Startup,
suppress_banner: bool,
rc_file: RcFile,
script_args: &Vec<String>) -> RuntimeResult<()>
{
let mut repl = Repl::new(color_mode, tab_width, startup, suppress_banner, rc_file, script_args);
repl.run()
}
fn interpret(buffer: &mut dyn Buffer,
startup: Startup,
color_mode: TtyColorMode,
tab_width: usize,
script_args: &Vec<String>) -> RuntimeResult<()>
{
let mut interpreter = Interpreter::new(startup,
Interactive::No,
color_mode,
tab_width,
RcFile::None,
script_args);
interpreter.execute_from_buffer(buffer)?;
Ok(())
}
fn run_script(script: &PathBuf,
tab_width: usize,
color_mode: TtyColorMode,
startup: Startup,
script_args: &Vec<String>) -> RuntimeResult<()>
{
let file = File::open(script);
if let Err(e) = file
{
return Err(RuntimeError::FileOpenError(script.clone(), e));
}
let file = file.unwrap();
let script = script.to_str().unwrap_or("input script");
let mut file_buffer = BufReader::new(file);
let mut buffer = ReadBuffer::new(&script, &mut file_buffer, Some(tab_width));
interpret(&mut buffer, startup, color_mode, tab_width, script_args)
}
fn run_code(code: &String,
tab_width: usize,
color_mode: TtyColorMode,
startup: Startup,
script_args: &Vec<String>) -> RuntimeResult<()>
{
let mut buffer = SimpleBuffer::new("command line", &code, Some(tab_width));
interpret(&mut buffer, startup, color_mode, tab_width, script_args)
}
fn run_stdin(tab_width: usize,
color_mode: TtyColorMode,
startup: Startup,
script_args: &Vec<String>) -> RuntimeResult<()>
{
let mut buffer = BufReader::new(stdin());
let mut buffer = ReadBuffer::new("standard input", &mut buffer, Some(tab_width));
interpret(&mut buffer, startup, color_mode, tab_width, script_args)
}
fn main() -> RuntimeResult<()>
{
let args = CommandArguments::parse();
if let Some(tab_width) = args.tab_width && tab_width == 0
{
return Err(RuntimeError::InvalidTabWidth);
}
let color_mode = determine_color_mode(&args);
let invoked_as_login = std::env::args_os()
.next()
.is_some_and(|name| name.as_encoded_bytes().starts_with(b"-"));
let rc_file = if let Some(custom_rc) = &args.rcfile
{
RcFile::Custom(custom_rc.clone())
}
else if args.norc
{
RcFile::None
}
else
{
RcFile::Default
};
let startup = if args.login || invoked_as_login
{
Startup::Login
}
else
{
Startup::NonLogin
};
let tab_width = args.tab_width.unwrap_or(4);
match determine_running_mode(&args)
{
RunningMode::Interactive => run_as_repl(tab_width,
color_mode,
startup,
args.no_banner,
rc_file,
&args.script_arguments),
RunningMode::Script => run_script(&args.script.unwrap(),
tab_width,
color_mode,
startup,
&args.script_arguments),
RunningMode::Code => run_code(&args.code.unwrap(),
tab_width,
color_mode,
startup,
&args.script_arguments),
RunningMode::Stdin => run_stdin(tab_width,
color_mode,
startup,
&args.script_arguments)
}
}