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
15pub trait Command: Any + fmt::Debug + dyn_clone::DynClone + 'static {
21 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}