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 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 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}