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