ghoti_exec/
command.rs

1use std::any::Any;
2use std::fmt;
3use std::marker::PhantomData;
4use std::pin::Pin;
5use std::rc::Rc;
6
7use ghoti_syntax::Stmt;
8
9use crate::{Error, ExecContext, FrameInfo, FrameKind, SourceFile, Status, VarScope};
10
11pub type BoxCommand = Box<dyn Command>;
12
13type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
14
15/// An executable command-like object.
16///
17/// Builtins and user functions are all its instances.
18///
19/// It must be cheaply clone-able.
20pub trait Command: Any + fmt::Debug + dyn_clone::DynClone + 'static {
21    // FIXME: Should be unnecessary since Rust 1.86.
22    fn as_any(&self) -> &dyn Any;
23
24    fn exec<'fut>(
25        &'fut self,
26        ctx: &'fut mut ExecContext<'_>,
27        args: &'fut [String],
28    ) -> BoxFuture<'fut, ()>;
29
30    fn description(&self) -> Option<&str> {
31        None
32    }
33}
34
35dyn_clone::clone_trait_object!(Command);
36
37#[expect(async_fn_in_trait, reason = "we accept !Send")]
38pub trait ReportResult {
39    async fn report(self, ctx: &mut ExecContext<'_>);
40}
41
42impl ReportResult for () {
43    async fn report(self, _ctx: &mut ExecContext<'_>) {}
44}
45
46impl ReportResult for bool {
47    async fn report(self, ctx: &mut ExecContext<'_>) {
48        ctx.set_last_status(Status::from(self))
49    }
50}
51
52impl ReportResult for Status {
53    async fn report(self, ctx: &mut ExecContext<'_>) {
54        ctx.set_last_status(self);
55    }
56}
57
58impl ReportResult for Error {
59    async fn report(self, ctx: &mut ExecContext<'_>) {
60        ctx.emit_error(self, true).await;
61    }
62}
63
64impl<T: ReportResult, E: ReportResult> ReportResult for Result<T, E> {
65    async fn report(self, ctx: &mut ExecContext<'_>) {
66        match self {
67            Ok(v) => v.report(ctx).await,
68            Err(e) => e.report(ctx).await,
69        }
70    }
71}
72
73impl<T: ReportResult> ReportResult for Option<T> {
74    async fn report(self, ctx: &mut ExecContext<'_>) {
75        if let Some(v) = self {
76            v.report(ctx).await;
77        }
78    }
79}
80
81struct RawBuiltin<F, Ret> {
82    func: F,
83    _marker: PhantomData<fn() -> Ret>,
84}
85
86impl<F, Ret> fmt::Debug for RawBuiltin<F, Ret> {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        f.debug_struct("Builtin").finish_non_exhaustive()
89    }
90}
91
92impl<F: Clone, Ret> Clone for RawBuiltin<F, Ret> {
93    fn clone(&self) -> Self {
94        Self {
95            func: self.func.clone(),
96            _marker: PhantomData,
97        }
98    }
99}
100
101impl<F, Ret> Command for RawBuiltin<F, Ret>
102where
103    F: 'static + Clone + AsyncFn(&mut ExecContext<'_>, &[String]) -> Ret,
104    Ret: 'static + ReportResult,
105{
106    fn as_any(&self) -> &dyn Any {
107        self
108    }
109
110    fn exec<'fut>(
111        &'fut self,
112        ctx: &'fut mut ExecContext<'_>,
113        args: &'fut [String],
114    ) -> BoxFuture<'fut, ()> {
115        Box::pin(async move { (self.func)(ctx, args).await.report(ctx).await })
116    }
117}
118
119pub fn raw_builtin<F, Ret>(func: F) -> impl Command
120where
121    F: 'static + Clone + AsyncFn(&mut ExecContext<'_>, &[String]) -> Ret,
122    Ret: 'static + ReportResult,
123{
124    RawBuiltin {
125        func,
126        _marker: PhantomData,
127    }
128}
129
130struct ParsedBuiltin<F, Args, Ret> {
131    func: F,
132    _marker: PhantomData<fn(Args) -> Ret>,
133}
134
135impl<F, Args, Ret> fmt::Debug for ParsedBuiltin<F, Args, Ret> {
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        f.debug_struct("Builtin").finish_non_exhaustive()
138    }
139}
140
141impl<F: Clone, Args, Ret> Clone for ParsedBuiltin<F, Args, Ret> {
142    fn clone(&self) -> Self {
143        Self {
144            func: self.func.clone(),
145            _marker: PhantomData,
146        }
147    }
148}
149
150impl<F, Args, Ret> Command for ParsedBuiltin<F, Args, Ret>
151where
152    F: 'static + Clone + AsyncFn(&mut ExecContext<'_>, Args) -> Ret,
153    Args: 'static + clap::Parser,
154    Ret: 'static + ReportResult,
155{
156    fn as_any(&self) -> &dyn Any {
157        self
158    }
159
160    fn exec<'fut>(
161        &'fut self,
162        ctx: &'fut mut ExecContext<'_>,
163        args: &'fut [String],
164    ) -> BoxFuture<'fut, ()> {
165        let ret = <Args as clap::Parser>::try_parse_from(args);
166        Box::pin(async move {
167            match ret {
168                Ok(parsed) => (self.func)(ctx, parsed).await.report(ctx).await,
169                Err(err) => ctx.emit_error(Error::InvalidOptions(err), true).await,
170            }
171        })
172    }
173}
174
175pub fn parsed_builtin<F, Args, Ret>(func: F) -> impl Command
176where
177    F: 'static + Clone + AsyncFn(&mut ExecContext<'_>, Args) -> Ret,
178    Args: 'static + clap::Parser,
179    Ret: 'static + ReportResult,
180{
181    ParsedBuiltin {
182        func,
183        _marker: PhantomData,
184    }
185}
186
187#[derive(Clone, Debug)]
188pub(crate) struct UserFunc(pub(crate) Rc<UserFuncImpl>);
189
190#[derive(Debug)]
191pub(crate) struct UserFuncImpl {
192    pub(crate) name: String,
193    pub(crate) description: Option<String>,
194    pub(crate) source: Rc<SourceFile>,
195    pub(crate) def_pos: u32,
196    pub(crate) stmt: Stmt,
197}
198
199impl UserFunc {
200    pub fn stmt(&self) -> &Stmt {
201        &self.0.stmt
202    }
203}
204
205impl Command for UserFunc {
206    fn as_any(&self) -> &dyn Any {
207        self
208    }
209
210    fn exec<'fut>(
211        &'fut self,
212        ctx: &'fut mut ExecContext<'_>,
213        args: &'fut [String],
214    ) -> BoxFuture<'fut, ()> {
215        Box::pin(async move {
216            let frame = FrameInfo {
217                kind: FrameKind::Function {
218                    name: self.0.name.clone(),
219                    def_pos: self.0.def_pos,
220                },
221                source: self.0.source.clone(),
222                pos: self.0.stmt.pos(),
223            };
224            let mut subctx = ExecContext::new_inside(ctx, frame);
225            subctx.set_var("argv", VarScope::Local, args[1..].to_vec());
226            subctx.exec_stmt(&self.0.stmt).await;
227        })
228    }
229
230    fn description(&self) -> Option<&str> {
231        self.0.description.as_deref()
232    }
233}