1#![doc(
2 html_logo_url = "https://graphix-lang.github.io/graphix/graphix-icon.svg",
3 html_favicon_url = "https://graphix-lang.github.io/graphix/graphix-icon.svg"
4)]
5use anyhow::{bail, Context, Result};
6use arcstr::ArcStr;
7use derive_builder::Builder;
8use enumflags2::BitFlags;
9use fxhash::FxHashMap;
10use graphix_compiler::{
11 env::Env,
12 expr::{CouldNotResolve, ExprId, ModuleResolver, Source},
13 format_with_flags,
14 typ::TVal,
15 CFlag, ExecCtx, PrintFlag,
16};
17use graphix_package::MainThreadHandle;
18use graphix_package_core::ProgramArgs;
19use graphix_rt::{CompExp, GXConfig, GXEvent, GXExt, GXHandle, GXRt};
20use input::InputReader;
21use netidx::{
22 publisher::{Publisher, Value},
23 subscriber::Subscriber,
24};
25use poolshark::global::GPooled;
26use reedline::Signal;
27use std::{marker::PhantomData, process::exit, time::Duration};
28use tokio::{select, sync::mpsc};
29
30mod completion;
31mod deps;
32mod input;
33
34enum Output<X: GXExt> {
35 None,
36 EmptyScript,
37 Custom(deps::Cdc<X>),
38 Text(CompExp<X>),
39}
40
41impl<X: GXExt> Output<X> {
42 async fn from_expr(
43 gx: &GXHandle<X>,
44 env: &Env,
45 e: CompExp<X>,
46 run_on_main: &MainThreadHandle,
47 ) -> Self {
48 match deps::maybe_init_custom(gx, env, e, run_on_main).await {
49 Err(e) => {
50 eprintln!("error initializing custom display: {e:?}");
51 Self::None
52 }
53 Ok(deps::CustomResult::Custom(cdc)) => Self::Custom(cdc),
54 Ok(deps::CustomResult::NotCustom(e)) => Self::Text(e),
55 }
56 }
57
58 async fn clear(&mut self) {
59 if let Self::Custom(cdc) = self {
60 cdc.custom.clear().await;
61 }
62 *self = Self::None;
63 }
64
65 async fn process_update(&mut self, env: &Env, id: ExprId, v: Value) {
66 match self {
67 Self::None | Output::EmptyScript => (),
68 Self::Custom(cdc) => cdc.custom.process_update(env, id, v).await,
69 Self::Text(e) => {
70 if e.id == id {
71 println!("{}", TVal { env: &env, typ: &e.typ, v: &v })
72 }
73 }
74 }
75 }
76}
77
78#[derive(Debug, Clone)]
79pub enum Mode {
80 Repl,
85 Script(Source),
89 Check(Source),
91}
92
93impl Mode {
94 fn file_mode(&self) -> bool {
95 match self {
96 Self::Repl => false,
97 Self::Script(_) | Self::Check(_) => true,
98 }
99 }
100}
101
102#[derive(Builder)]
103#[builder(pattern = "owned")]
104pub struct Shell<X: GXExt> {
105 #[builder(default = "false")]
107 no_init: bool,
108 #[builder(setter(strip_option), default)]
110 publish_timeout: Option<Duration>,
111 #[builder(setter(strip_option), default)]
114 resolve_timeout: Option<Duration>,
115 #[builder(default)]
117 module_resolvers: Vec<ModuleResolver>,
118 #[builder(default = "Mode::Repl")]
120 mode: Mode,
121 publisher: Publisher,
125 subscriber: Subscriber,
129 #[builder(default)]
132 enable_flags: BitFlags<CFlag>,
133 #[builder(default)]
136 disable_flags: BitFlags<CFlag>,
137 #[builder(default)]
139 program_args: Vec<ArcStr>,
140 #[builder(setter(skip), default)]
141 _phantom: PhantomData<X>,
142}
143
144impl<X: GXExt> Shell<X> {
145 async fn init(
146 &mut self,
147 sub: mpsc::Sender<GPooled<Vec<GXEvent>>>,
148 ) -> Result<GXHandle<X>> {
149 let publisher = self.publisher.clone();
150 let subscriber = self.subscriber.clone();
151 let mut ctx = ExecCtx::new(GXRt::<X>::new(publisher, subscriber))
152 .context("creating graphix context")?;
153 let mut args = vec![];
154 if let Mode::Script(source) | Mode::Check(source) = &self.mode {
155 if let Source::File(p) = source {
156 args.push(ArcStr::from(p.display().to_string().as_str()));
157 }
158 }
159 args.extend(self.program_args.drain(..));
160 if !args.is_empty() {
161 ctx.libstate.set(ProgramArgs(args));
162 }
163 let mut vfs_modules = FxHashMap::default();
164 let result = deps::register::<X>(&mut ctx, &mut vfs_modules)
165 .context("register package modules")?;
166 if let Some(main) = result.main_program {
167 if matches!(self.mode, Mode::Repl) {
168 self.mode = Mode::Script(Source::Internal(ArcStr::from(main)));
169 }
170 }
171 let mut flags = match self.mode {
172 Mode::Script(_) | Mode::Check(_) => CFlag::WarnUnhandled | CFlag::WarnUnused,
173 Mode::Repl => BitFlags::empty(),
174 };
175 flags.insert(self.enable_flags);
176 flags.remove(self.disable_flags);
177 let mut mods = vec![ModuleResolver::VFS(vfs_modules)];
178 for res in self.module_resolvers.drain(..) {
179 mods.push(res);
180 }
181 let mut gx = GXConfig::builder(ctx, sub);
182 gx = gx.flags(flags);
183 if let Some(s) = self.publish_timeout {
184 gx = gx.publish_timeout(s);
185 }
186 if let Some(s) = self.resolve_timeout {
187 gx = gx.resolve_timeout(s);
188 }
189 let handle = gx
190 .root(result.root)
191 .resolvers(mods)
192 .build()
193 .context("building rt config")?
194 .start()
195 .await
196 .context("loading initial modules")?;
197 Ok(handle)
198 }
199
200 async fn load_env(
201 &mut self,
202 gx: &GXHandle<X>,
203 newenv: &mut Option<Env>,
204 output: &mut Output<X>,
205 exprs: &mut Vec<CompExp<X>>,
206 run_on_main: &MainThreadHandle,
207 ) -> Result<Env> {
208 let env;
209 match &self.mode {
210 Mode::Check(source) => {
211 gx.check(source.clone()).await?;
212 exit(0)
213 }
214 Mode::Script(source) => {
215 let r = gx.load(source.clone()).await?;
216 exprs.extend(r.exprs);
217 env = gx.get_env().await?;
218 if let Some(e) = exprs.pop() {
219 *output = Output::from_expr(&gx, &env, e, run_on_main).await;
220 }
221 *newenv = None
222 }
223 Mode::Repl if !self.no_init => match gx.compile("mod init".into()).await {
224 Ok(res) => {
225 env = res.env;
226 exprs.extend(res.exprs);
227 *newenv = Some(env.clone())
228 }
229 Err(e) if e.is::<CouldNotResolve>() => {
230 env = gx.get_env().await?;
231 *newenv = Some(env.clone())
232 }
233 Err(e) => {
234 eprintln!("error in init module: {e:?}");
235 env = gx.get_env().await?;
236 *newenv = Some(env.clone())
237 }
238 },
239 Mode::Repl => {
240 env = gx.get_env().await?;
241 *newenv = Some(env.clone());
242 }
243 }
244 Ok(env)
245 }
246
247 pub async fn run(mut self, run_on_main: MainThreadHandle) -> Result<()> {
248 let (tx, mut from_gx) = mpsc::channel(100);
249 let gx = self.init(tx).await?;
250 let script = self.mode.file_mode();
251 let mut input = InputReader::new();
252 let mut output = if script { Output::EmptyScript } else { Output::None };
253 let mut newenv = None;
254 let mut exprs = vec![];
255 let mut env = self
256 .load_env(&gx, &mut newenv, &mut output, &mut exprs, &run_on_main)
257 .await?;
258 if !script {
259 println!("Welcome to the graphix shell");
260 println!("Press ctrl-c to cancel, ctrl-d to exit, and tab for help")
261 }
262 loop {
263 select! {
264 batch = from_gx.recv() => match batch {
265 None => bail!("graphix runtime is dead"),
266 Some(mut batch) => {
267 for e in batch.drain(..) {
268 match e {
269 GXEvent::Updated(id, v) => {
270 output.process_update(&env, id, v).await
271 },
272 GXEvent::Env(e) => {
273 env = e;
274 newenv = Some(env.clone());
275 }
276 }
277 }
278 }
279 },
280 input = input.read_line(&mut output, &mut newenv) => {
281 match input {
282 Err(e) => eprintln!("error reading line {e:?}"),
283 Ok(Signal::CtrlC) if script => break Ok(()),
284 Ok(Signal::CtrlC) => {
285 output.clear().await;
286 }
287 Ok(Signal::CtrlD) => break Ok(()),
288 Ok(Signal::Success(line)) => {
289 match gx.compile(ArcStr::from(line)).await {
290 Err(e) => eprintln!("error: {e:?}"),
291 Ok(res) => {
292 env = res.env;
293 newenv = Some(env.clone());
294 exprs.extend(res.exprs);
295 if exprs.last().map(|e| e.output).unwrap_or(false) {
296 let e = exprs.pop().unwrap();
297 let typ = e.typ
298 .with_deref(|t| t.cloned())
299 .unwrap_or_else(|| e.typ.clone());
300 format_with_flags(
301 PrintFlag::DerefTVars | PrintFlag::ReplacePrims,
302 || println!("-: {}", typ)
303 );
304 output.clear().await;
305 output = Output::from_expr(
306 &gx, &env, e, &run_on_main,
307 ).await;
308 } else {
309 output.clear().await;
310 }
311 }
312 }
313 }
314 }
315 },
316 }
317 }
318 }
319}