use anyhow::Result;
use async_channel::{Receiver, Sender};
use endbasic_client::CloudService;
use endbasic_std::console::ConsoleFactory;
use endbasic_std::storage::Storage;
use endbasic_std::{Error as StdError, MachineBuilder, Signal};
use getoptsargs::prelude::*;
use std::cell::RefCell;
use std::fs::File;
use std::path::Path;
use std::rc::Rc;
mod console;
pub use console::setup_console;
mod gpio;
use gpio::setup_gpio_pins;
mod propline;
pub use propline::{PropLine, extract_propline};
mod storage;
use storage::{get_local_drive_spec, setup_storage};
const INTERRUPTED_EXIT_CODE: i32 = 128 + 2;
fn new_machine_builder(
console_factory: Box<dyn ConsoleFactory>,
signals_chan: (Sender<Signal>, Receiver<Signal>),
gpio_pins_spec: Option<&str>,
) -> Result<MachineBuilder> {
let console = console_factory.build()?;
let mut builder = MachineBuilder::default();
builder = builder.with_console(console);
builder = builder.with_signals_chan(signals_chan);
builder = builder.with_gpio_pins(setup_gpio_pins(gpio_pins_spec)?);
Ok(builder)
}
pub async fn run_repl_loop(
console_factory: Box<dyn ConsoleFactory>,
signals_chan: (Sender<Signal>, Receiver<Signal>),
gpio_pins_spec: Option<&str>,
local_drive_spec: &str,
service_url: &str,
) -> Result<i32> {
let mut builder = new_machine_builder(console_factory, signals_chan, gpio_pins_spec)?;
let console = builder.get_console();
let program = Rc::from(RefCell::from(endbasic_repl::editor::Editor::default()));
let storage = Rc::from(RefCell::from(Storage::default()));
setup_storage(&mut storage.borrow_mut(), local_drive_spec)?;
let service = Rc::from(RefCell::from(CloudService::new(service_url)?));
endbasic_client::add_all(
&mut builder,
service,
console.clone(),
storage.clone(),
"https://repl.endbasic.dev/",
);
let mut machine = builder
.make_interactive()
.with_program(program.clone())
.with_storage(storage.clone())
.build();
endbasic_repl::print_welcome(&mut *console.borrow_mut())?;
endbasic_repl::try_load_autoexec(&mut machine, console.clone(), storage).await?;
Ok(endbasic_repl::run_repl_loop(&mut machine, console, program).await?)
}
pub async fn run_script<P: AsRef<Path>>(
path: P,
console_factory: Box<dyn ConsoleFactory>,
signals_chan: (Sender<Signal>, Receiver<Signal>),
gpio_pins_spec: Option<&str>,
) -> Result<i32> {
let builder = new_machine_builder(console_factory, signals_chan, gpio_pins_spec)?;
let mut machine = builder.build();
let mut input = File::open(path)?;
machine.compile(&mut input)?;
match machine.exec().await {
Ok(Some(code)) => Ok(code),
Ok(None) => Ok(0),
Err(StdError::Break) => Ok(INTERRUPTED_EXIT_CODE),
Err(e) => Err(e.into()),
}
}
pub async fn run_interactive(
path: &str,
console_factory: Box<dyn ConsoleFactory>,
signals_chan: (Sender<Signal>, Receiver<Signal>),
gpio_pins_spec: Option<&str>,
local_drive_spec: &str,
service_url: &str,
) -> Result<i32> {
let mut builder = new_machine_builder(console_factory, signals_chan, gpio_pins_spec)?;
let console = builder.get_console();
let program = Rc::from(RefCell::from(endbasic_repl::editor::Editor::default()));
let storage = Rc::from(RefCell::from(Storage::default()));
setup_storage(&mut storage.borrow_mut(), local_drive_spec)?;
let service = Rc::from(RefCell::from(CloudService::new(service_url)?));
endbasic_client::add_all(
&mut builder,
service,
console.clone(),
storage.clone(),
"https://repl.endbasic.dev/",
);
let mut machine = builder
.make_interactive()
.with_program(program.clone())
.with_storage(storage.clone())
.build();
match path.strip_prefix("cloud://") {
Some(username_path) => {
let path =
endbasic_repl::mount_cloud_share(console.clone(), storage.clone(), username_path)?;
let code = endbasic_repl::run_from_storage_path(
&mut machine,
console.clone(),
storage.clone(),
program.clone(),
&path,
false,
)
.await?;
Ok(code)
}
None => {
let mut input = File::open(path)?;
machine.compile(&mut input)?;
match machine.exec().await {
Ok(Some(code)) => Ok(code),
Ok(None) => Ok(0),
Err(StdError::Break) => Ok(INTERRUPTED_EXIT_CODE),
Err(e) => Err(e.into()),
}
}
}
}
pub enum AppMode {
Repl(String),
RunInteractive(String, String),
RunScript(String),
}
impl AppMode {
pub fn from_matches(matches: Matches) -> Result<AppMode> {
match matches.arg_trail() {
[] => {
let local_drive = get_local_drive_spec(matches.opt_str("local-drive"))?;
Ok(AppMode::Repl(local_drive))
}
[file] => {
if matches.opt_present("interactive") {
let local_drive = get_local_drive_spec(matches.opt_str("local-drive"))?;
Ok(AppMode::RunInteractive((*file).to_owned(), local_drive))
} else {
Ok(AppMode::RunScript((*file).to_owned()))
}
}
[_, ..] => Err(bad_usage!("Too many arguments").into()),
}
}
}
pub async fn async_app_main(
app_mode: AppMode,
console_factory: Box<dyn ConsoleFactory>,
signals_chan: (Sender<Signal>, Receiver<Signal>),
gpio_pins_spec: Option<String>,
service_url: String,
) -> Result<i32> {
match app_mode {
AppMode::Repl(local_drive) => Ok(run_repl_loop(
console_factory,
signals_chan,
gpio_pins_spec.as_deref(),
&local_drive,
&service_url,
)
.await?),
AppMode::RunInteractive(file, local_drive) => Ok(run_interactive(
&file,
console_factory,
signals_chan,
gpio_pins_spec.as_deref(),
&local_drive,
&service_url,
)
.await?),
AppMode::RunScript(file) => {
Ok(run_script(file, console_factory, signals_chan, gpio_pins_spec.as_deref()).await?)
}
}
}