Skip to main content

mittens_engine/scripting/repl/
backend.rs

1use std::collections::VecDeque;
2
3use crate::engine::{ecs, graphics};
4use crate::scripting::object::Value;
5use crate::scripting::repl::{
6    MeowMeowReplFrontend, NavigationState, ReplInput, format_repl_value, parse_repl_input,
7};
8use crate::scripting::world_evaluator::{
9    EvalRequest, EvalResponse, HostCallKind, HostValue, MeowMeowEvaluator, MeowMeowEvaluatorHandle,
10};
11
12enum ActiveInput {
13    Snippet {
14        source: String,
15    },
16    Navigation {
17        binding: Option<String>,
18        tail: Vec<String>,
19    },
20}
21
22pub struct MeowMeowRepl {
23    frontend: MeowMeowReplFrontend,
24    evaluator: MeowMeowEvaluatorHandle,
25    pending: VecDeque<String>,
26    active: Option<ActiveInput>,
27    navigation: NavigationState,
28}
29
30impl MeowMeowRepl {
31    pub fn new() -> Result<Self, &'static str> {
32        Ok(Self {
33            frontend: MeowMeowReplFrontend::new()?,
34            evaluator: MeowMeowEvaluator::spawn(128),
35            pending: VecDeque::new(),
36            active: None,
37            navigation: NavigationState::new(),
38        })
39    }
40
41    pub fn sync(
42        &mut self,
43        world: &mut ecs::World,
44        rx: &mut ecs::RxWorld,
45        render_assets: &mut graphics::RenderAssets,
46        emit: &mut dyn ecs::SignalEmitter,
47    ) {
48        if let Some(message) = self.navigation.ensure_valid(world) {
49            eprintln!("[mms] {message}");
50        }
51        self.pending.extend(self.frontend.try_recv_all());
52        self.start_next(world);
53        while let Ok(response) = self.evaluator.responses.pop() {
54            match response {
55                EvalResponse::HostCall { id, kind } => {
56                    let reply = service_host_call(kind, world, rx, render_assets, emit);
57                    while self
58                        .evaluator
59                        .requests
60                        .push(EvalRequest::HostCallResult {
61                            id,
62                            value: reply.clone(),
63                        })
64                        .is_err()
65                    {
66                        std::thread::yield_now();
67                    }
68                }
69                EvalResponse::Intent(intent) => {
70                    emit.push_intent_now(ecs::ComponentId::default(), intent)
71                }
72                EvalResponse::SnippetComplete { result } => {
73                    let source = match self.active.take() {
74                        Some(ActiveInput::Snippet { source }) => source,
75                        _ => String::new(),
76                    };
77                    match result {
78                        Ok(Some(Value::Null)) if is_control_call(&source) => {}
79                        Ok(Some(value)) => match format_repl_value(&value, world) {
80                            Ok(text) => println!("{text}"),
81                            Err(error) => eprintln!("[mms] {error}"),
82                        },
83                        Ok(None) => {}
84                        Err(error) => eprintln!("[mms] {error}"),
85                    }
86                }
87                EvalResponse::NavigationComplete { result } => {
88                    let (binding, tail) = match self.active.take() {
89                        Some(ActiveInput::Navigation { binding, tail }) => (binding, tail),
90                        _ => (None, Vec::new()),
91                    };
92                    match result {
93                        Ok(value) => {
94                            let previous = self.navigation.clone();
95                            let result = self
96                                .navigation
97                                .set_evaluated(value, binding, world)
98                                .and_then(|()| {
99                                    for segment in &tail {
100                                        self.navigation.cd_child(segment, world)?;
101                                    }
102                                    Ok(())
103                                });
104                            if let Err(error) = result {
105                                self.navigation = previous;
106                                eprintln!("[mms] {error}");
107                            }
108                        }
109                        Err(error) => eprintln!("[mms] {error}"),
110                    }
111                }
112                EvalResponse::ReplReset => self.navigation.reset(),
113                EvalResponse::Error { message } => eprintln!("[mms] {message}"),
114                EvalResponse::ParsedOk { .. } | EvalResponse::ShutdownAck => {}
115            }
116        }
117        self.start_next(world);
118    }
119
120    fn start_next(&mut self, world: &ecs::World) {
121        if self.active.is_some() {
122            return;
123        }
124        while let Some(source) = self.pending.pop_front() {
125            match parse_repl_input(source) {
126                ReplInput::Ls => match self.navigation.listing(world) {
127                    Ok(lines) if lines.is_empty() => println!("(empty)"),
128                    Ok(lines) => lines.into_iter().for_each(|line| println!("{line}")),
129                    Err(error) => eprintln!("[mms] ls: {error}"),
130                },
131                ReplInput::Pwd => println!("{}", self.navigation.pwd(world)),
132                ReplInput::Cd(operand) => {
133                    if self.start_cd(operand, world) {
134                        return;
135                    }
136                }
137                ReplInput::Snippet(source) => {
138                    let request = EvalRequest::EvalSnippet {
139                        source: source.clone(),
140                        cwd: Some(self.navigation.cwd_value()),
141                    };
142                    if self.evaluator.requests.push(request).is_ok() {
143                        self.active = Some(ActiveInput::Snippet { source });
144                    } else {
145                        self.pending.push_front(source);
146                    }
147                    return;
148                }
149            }
150        }
151    }
152
153    /// Returns true when an asynchronous expression evaluation was started.
154    fn start_cd(&mut self, operand: String, world: &ecs::World) -> bool {
155        match operand.as_str() {
156            "/" => {
157                self.navigation.cd_root();
158                return false;
159            }
160            "." => return false,
161            ".." => {
162                self.navigation.cd_parent(world);
163                return false;
164            }
165            _ if operand.contains('/') => {
166                if let Err(error) = self.navigation.cd_path(&operand, world) {
167                    eprintln!("[mms] cd: {error}");
168                }
169                return false;
170            }
171            _ => {}
172        }
173
174        // Simple names are children first. Dotted identifier chains are a
175        // binding anchor followed by deterministic table navigation.
176        let identifiers = operand.split('.').collect::<Vec<_>>();
177        let is_dotted_path = identifiers.len() > 1
178            && identifiers.iter().all(|part| {
179                !part.is_empty() && part.chars().all(|c| c == '_' || c.is_alphanumeric())
180            });
181        if !is_dotted_path && !operand.contains(['(', '[', '{', '"', '\'', ' ']) {
182            if self.navigation.cd_child(&operand, world).is_ok() {
183                return false;
184            }
185        }
186        let (source, binding, tail) = if is_dotted_path {
187            (
188                identifiers[0].to_string(),
189                Some(identifiers[0].to_string()),
190                identifiers[1..]
191                    .iter()
192                    .map(|part| (*part).to_string())
193                    .collect(),
194            )
195        } else {
196            let binding =
197                (operand != "cwd" && is_simple_identifier(&operand)).then(|| operand.clone());
198            (operand, binding, Vec::new())
199        };
200        let request = EvalRequest::EvalNavigation {
201            source,
202            cwd: Some(self.navigation.cwd_value()),
203        };
204        match self.evaluator.requests.push(request) {
205            Ok(()) => {
206                self.active = Some(ActiveInput::Navigation { binding, tail });
207                true
208            }
209            Err(_) => false,
210        }
211    }
212}
213
214fn is_simple_identifier(value: &str) -> bool {
215    let mut chars = value.chars();
216    chars.next().is_some_and(|c| c == '_' || c.is_alphabetic())
217        && chars.all(|c| c == '_' || c.is_alphanumeric())
218}
219
220fn is_control_call(source: &str) -> bool {
221    matches!(
222        source.trim().split('(').next().unwrap_or(""),
223        "tree" | "dump" | "help" | "clear" | "reset"
224    )
225}
226
227fn service_host_call(
228    kind: HostCallKind,
229    world: &mut ecs::World,
230    rx: &mut ecs::RxWorld,
231    render_assets: &mut graphics::RenderAssets,
232    emit: &mut dyn ecs::SignalEmitter,
233) -> HostValue {
234    match kind {
235        HostCallKind::Spawn(ce) => {
236            crate::scripting::component_registry::with_live_render_assets(render_assets, || {
237                crate::scripting::component_registry::spawn_tree(&ce, None, world, emit)
238            })
239            .map(HostValue::ComponentId)
240            .unwrap_or_else(|e| {
241                eprintln!("[mms] spawn: {e}");
242                HostValue::Null
243            })
244        }
245        HostCallKind::Register(ce) => {
246            crate::scripting::component_registry::with_live_render_assets(render_assets, || {
247                crate::scripting::component_registry::spawn_tree_uninitialized(&ce, world, emit)
248            })
249            .map(HostValue::ComponentId)
250            .unwrap_or_else(|e| {
251                eprintln!("[mms] register: {e}");
252                HostValue::Null
253            })
254        }
255        HostCallKind::Attach { parent, child } => {
256            if let Some(parent) = parent {
257                if let Err(e) = world.add_child(parent, child) {
258                    eprintln!("[mms] attach: {e}");
259                    return HostValue::Null;
260                }
261            }
262            world.init_component_tree(child, emit);
263            HostValue::Null
264        }
265        HostCallKind::Query {
266            selector,
267            scope,
268            multiple,
269        } => {
270            if let Some(id) = scope
271                && world.get_component_record(id).is_none()
272            {
273                eprintln!("[mms] stale component handle: component {id:?} is not live");
274                return HostValue::Null;
275            }
276            let roots = scope
277                .map(|id| vec![id])
278                .unwrap_or_else(|| world.world_roots());
279            let mut ids = Vec::new();
280            for root in roots {
281                if multiple {
282                    ids.extend(world.find_all_components(root, &selector));
283                } else if let Some(id) = world.find_component(root, &selector) {
284                    ids.push(id);
285                    break;
286                }
287            }
288            if multiple {
289                HostValue::ComponentList(
290                    ids.into_iter()
291                        .filter_map(|id| {
292                            world.component_name(id).map(|name| (id, name.to_string()))
293                        })
294                        .collect(),
295                )
296            } else {
297                ids.into_iter()
298                    .next()
299                    .and_then(|id| {
300                        world.component_name(id).map(|name| HostValue::Component {
301                            id,
302                            component_type: name.to_string(),
303                        })
304                    })
305                    .unwrap_or(HostValue::Null)
306            }
307        }
308        HostCallKind::ReplDump { value } => {
309            dump_value(&value, world);
310            HostValue::Null
311        }
312        HostCallKind::ReplTree { value, max_depth } => {
313            tree_value(&value, world, max_depth.unwrap_or(usize::MAX));
314            HostValue::Null
315        }
316        HostCallKind::ReplHelp => {
317            println!("MMS REPL: persistent let bindings, expression echo, and live mutation");
318            println!("ls, pwd, cd /, cd .., cd path/to/item, cd <MMS expression>");
319            println!(
320                "Navigation supports tables, arrays, live components, and detached component expressions."
321            );
322            println!("cwd is the current value; table fields are implicit after lexical bindings.");
323            println!("query(\"#name\"), query_all(world, \"Type\"), component.query_all(\"Text\")");
324            println!(
325                "At a live component, query(\"Text\") is scoped; query(world, \"Text\") is global."
326            );
327            println!("tree(value[, max_depth]), dump(value), clear(), reset(), help()");
328            HostValue::Null
329        }
330        HostCallKind::ReplClear => {
331            print!("\x1b[2J\x1b[H");
332            HostValue::Null
333        }
334        HostCallKind::RegisterHandler {
335            scope,
336            signal_kind,
337            name,
338            handler,
339        } => {
340            let callback = move |world: &mut ecs::World,
341                                 emit: &mut dyn ecs::SignalEmitter,
342                                 signal: &ecs::Signal| {
343                let arg = crate::scripting::runner::event_arg_value(signal);
344                if let Err(e) = crate::scripting::world_evaluator::eval_mms_fn(
345                    &handler,
346                    vec![arg],
347                    None,
348                    Some(world),
349                    Some(emit),
350                ) {
351                    eprintln!("[mms] handler: {e}");
352                }
353            };
354            if let Some(name) = name {
355                rx.add_handler_closure_named(signal_kind, scope, Some(name), callback);
356            } else {
357                rx.add_handler_closure(signal_kind, scope, callback);
358            }
359            HostValue::Null
360        }
361        HostCallKind::AudioClipInstance {
362            source,
363            start_beat,
364            stop_beat,
365        } => {
366            use crate::engine::ecs::component::AudioClipComponent;
367            let Some(src) = world.get_component_by_id_as::<AudioClipComponent>(source) else {
368                return HostValue::Null;
369            };
370            let mut component = AudioClipComponent::instance_of(src);
371            if let Some(v) = start_beat {
372                component.start_beat = v;
373            }
374            if let Some(v) = stop_beat {
375                component.stop_beat = Some(v);
376            }
377            HostValue::ComponentId(world.add_component(component))
378        }
379        HostCallKind::InvokeComponentMethod {
380            id,
381            component_type,
382            method,
383            args,
384        } => {
385            match crate::scripting::component_method_registry::invoke_component_method(
386                world,
387                id,
388                &component_type,
389                &method,
390                &args,
391                |intent| emit.push_intent_now(id, intent),
392            ) {
393                Ok(Value::ComponentObject { id, component_type }) => {
394                    HostValue::Component { id, component_type }
395                }
396                Ok(_) => HostValue::Null,
397                Err(e) => {
398                    eprintln!("[mms] {e}");
399                    HostValue::Null
400                }
401            }
402        }
403    }
404}
405
406fn selected_ids(value: &Value, world: &ecs::World) -> Result<Vec<ecs::ComponentId>, String> {
407    match value {
408        Value::Identifier(name) if name == "__mms_world__" => Ok(world.world_roots()),
409        Value::ComponentObject { id, .. } if world.get_component_record(*id).is_some() => {
410            Ok(vec![*id])
411        }
412        Value::ComponentObject { id, .. } => Err(format!(
413            "stale component handle: component {id:?} is not live"
414        )),
415        Value::Array(items) => {
416            let mut ids = Vec::new();
417            for item in items {
418                ids.extend(selected_ids(item, world)?);
419            }
420            Ok(ids)
421        }
422        other => Err(format!(
423            "expected world, component, or component array, got {other:?}"
424        )),
425    }
426}
427
428fn dump_value(value: &Value, world: &ecs::World) {
429    match selected_ids(value, world) {
430        Ok(ids) => {
431            for id in ids {
432                match crate::scripting::component_registry::subtree_to_ce_ast(world, id) {
433                    Ok(ce) => println!("{}", crate::scripting::unparser::unparse_component(&ce)),
434                    Err(e) => eprintln!("[mms] {e}"),
435                }
436            }
437        }
438        Err(_) => match format_repl_value(value, world) {
439            Ok(v) => println!("{v}"),
440            Err(e) => eprintln!("[mms] {e}"),
441        },
442    }
443}
444
445fn tree_value(value: &Value, world: &ecs::World, max_depth: usize) {
446    match selected_ids(value, world) {
447        Ok(ids) => {
448            for id in ids {
449                print_tree(world, id, 0, max_depth);
450            }
451        }
452        Err(e) => eprintln!("[mms] {e}"),
453    }
454}
455
456fn print_tree(world: &ecs::World, id: ecs::ComponentId, depth: usize, max_depth: usize) {
457    let name = world.component_name(id).unwrap_or("<deleted>");
458    let authored = world
459        .get_component_node(id)
460        .map(|node| node.name.as_str())
461        .unwrap_or("");
462    println!(
463        "{}{} {:?}{}",
464        "  ".repeat(depth),
465        name,
466        id,
467        if authored.is_empty() || authored == name {
468            String::new()
469        } else {
470            format!(" #{authored}")
471        }
472    );
473    if depth < max_depth {
474        for child in world.children_of(id) {
475            print_tree(world, *child, depth + 1, max_depth);
476        }
477    }
478}