1use anyhow::Result;
20use async_channel::{Receiver, Sender};
21use endbasic_client::CloudService;
22use endbasic_std::console::ConsoleFactory;
23use endbasic_std::storage::Storage;
24use endbasic_std::{Error as StdError, MachineBuilder, Signal};
25use getoptsargs::prelude::*;
26use std::cell::RefCell;
27use std::fs::File;
28use std::path::Path;
29use std::rc::Rc;
30
31mod console;
32pub use console::setup_console;
33
34mod gpio;
35use gpio::setup_gpio_pins;
36
37mod propline;
38pub use propline::{PropLine, extract_propline};
39
40mod storage;
41use storage::{get_local_drive_spec, setup_storage};
42
43const INTERRUPTED_EXIT_CODE: i32 = 128 + 2;
45
46fn new_machine_builder(
48 console_factory: Box<dyn ConsoleFactory>,
49 signals_chan: (Sender<Signal>, Receiver<Signal>),
50 gpio_pins_spec: Option<&str>,
51) -> Result<MachineBuilder> {
52 let console = console_factory.build()?;
53 let mut builder = MachineBuilder::default();
54 builder = builder.with_console(console);
55 builder = builder.with_signals_chan(signals_chan);
56 builder = builder.with_gpio_pins(setup_gpio_pins(gpio_pins_spec)?);
57 Ok(builder)
58}
59
60pub async fn run_repl_loop(
65 console_factory: Box<dyn ConsoleFactory>,
66 signals_chan: (Sender<Signal>, Receiver<Signal>),
67 gpio_pins_spec: Option<&str>,
68 local_drive_spec: &str,
69 service_url: &str,
70) -> Result<i32> {
71 let mut builder = new_machine_builder(console_factory, signals_chan, gpio_pins_spec)?;
72 let console = builder.get_console();
73 let program = Rc::from(RefCell::from(endbasic_repl::editor::Editor::default()));
74 let storage = Rc::from(RefCell::from(Storage::default()));
75 setup_storage(&mut storage.borrow_mut(), local_drive_spec)?;
76
77 let service = Rc::from(RefCell::from(CloudService::new(service_url)?));
78 endbasic_client::add_all(
79 &mut builder,
80 service,
81 console.clone(),
82 storage.clone(),
83 "https://repl.endbasic.dev/",
84 );
85
86 let mut machine = builder
87 .make_interactive()
88 .with_program(program.clone())
89 .with_storage(storage.clone())
90 .build();
91 endbasic_repl::print_welcome(&mut *console.borrow_mut())?;
92 endbasic_repl::try_load_autoexec(&mut machine, console.clone(), storage).await?;
93 Ok(endbasic_repl::run_repl_loop(&mut machine, console, program).await?)
94}
95
96pub async fn run_script<P: AsRef<Path>>(
98 path: P,
99 console_factory: Box<dyn ConsoleFactory>,
100 signals_chan: (Sender<Signal>, Receiver<Signal>),
101 gpio_pins_spec: Option<&str>,
102) -> Result<i32> {
103 let builder = new_machine_builder(console_factory, signals_chan, gpio_pins_spec)?;
104 let mut machine = builder.build();
105 let mut input = File::open(path)?;
106
107 machine.compile(&mut input)?;
108 match machine.exec().await {
109 Ok(Some(code)) => Ok(code),
110 Ok(None) => Ok(0),
111 Err(StdError::Break) => Ok(INTERRUPTED_EXIT_CODE),
112 Err(e) => Err(e.into()),
113 }
114}
115
116pub async fn run_interactive(
125 path: &str,
126 console_factory: Box<dyn ConsoleFactory>,
127 signals_chan: (Sender<Signal>, Receiver<Signal>),
128 gpio_pins_spec: Option<&str>,
129 local_drive_spec: &str,
130 service_url: &str,
131) -> Result<i32> {
132 let mut builder = new_machine_builder(console_factory, signals_chan, gpio_pins_spec)?;
133 let console = builder.get_console();
134 let program = Rc::from(RefCell::from(endbasic_repl::editor::Editor::default()));
135 let storage = Rc::from(RefCell::from(Storage::default()));
136 setup_storage(&mut storage.borrow_mut(), local_drive_spec)?;
137
138 let service = Rc::from(RefCell::from(CloudService::new(service_url)?));
139 endbasic_client::add_all(
140 &mut builder,
141 service,
142 console.clone(),
143 storage.clone(),
144 "https://repl.endbasic.dev/",
145 );
146
147 let mut machine = builder
148 .make_interactive()
149 .with_program(program.clone())
150 .with_storage(storage.clone())
151 .build();
152
153 match path.strip_prefix("cloud://") {
154 Some(username_path) => {
155 let path =
156 endbasic_repl::mount_cloud_share(console.clone(), storage.clone(), username_path)?;
157 let code = endbasic_repl::run_from_storage_path(
158 &mut machine,
159 console.clone(),
160 storage.clone(),
161 program.clone(),
162 &path,
163 false,
164 )
165 .await?;
166 Ok(code)
167 }
168 None => {
169 let mut input = File::open(path)?;
170 machine.compile(&mut input)?;
171 match machine.exec().await {
172 Ok(Some(code)) => Ok(code),
173 Ok(None) => Ok(0),
174 Err(StdError::Break) => Ok(INTERRUPTED_EXIT_CODE),
175 Err(e) => Err(e.into()),
176 }
177 }
178 }
179}
180
181pub enum AppMode {
183 Repl(String),
187
188 RunInteractive(String, String),
193
194 RunScript(String),
198}
199
200impl AppMode {
201 pub fn from_matches(matches: Matches) -> Result<AppMode> {
203 match matches.arg_trail() {
204 [] => {
205 let local_drive = get_local_drive_spec(matches.opt_str("local-drive"))?;
206 Ok(AppMode::Repl(local_drive))
207 }
208 [file] => {
209 if matches.opt_present("interactive") {
210 let local_drive = get_local_drive_spec(matches.opt_str("local-drive"))?;
211 Ok(AppMode::RunInteractive((*file).to_owned(), local_drive))
212 } else {
213 Ok(AppMode::RunScript((*file).to_owned()))
214 }
215 }
216 [_, ..] => Err(bad_usage!("Too many arguments").into()),
217 }
218 }
219}
220
221pub async fn async_app_main(
223 app_mode: AppMode,
224 console_factory: Box<dyn ConsoleFactory>,
225 signals_chan: (Sender<Signal>, Receiver<Signal>),
226 gpio_pins_spec: Option<String>,
227 service_url: String,
228) -> Result<i32> {
229 match app_mode {
230 AppMode::Repl(local_drive) => Ok(run_repl_loop(
231 console_factory,
232 signals_chan,
233 gpio_pins_spec.as_deref(),
234 &local_drive,
235 &service_url,
236 )
237 .await?),
238 AppMode::RunInteractive(file, local_drive) => Ok(run_interactive(
239 &file,
240 console_factory,
241 signals_chan,
242 gpio_pins_spec.as_deref(),
243 &local_drive,
244 &service_url,
245 )
246 .await?),
247 AppMode::RunScript(file) => {
248 Ok(run_script(file, console_factory, signals_chan, gpio_pins_spec.as_deref()).await?)
249 }
250 }
251}