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