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