1use std::collections::HashMap;
9
10pub struct HelpSystem {
14 topics: HashMap<String, String>,
15 color_enabled: bool,
16}
17
18impl HelpSystem {
19 pub fn new(color_enabled: bool) -> Self {
25 let mut topics = HashMap::new();
26
27 topics.insert("basics".to_string(), Self::generate_basics_topic(color_enabled));
29 topics.insert("evaluation".to_string(), Self::generate_evaluation_topic(color_enabled));
30 topics.insert("keyboard".to_string(), Self::generate_keyboard_topic(color_enabled));
31 topics.insert("sessions".to_string(), Self::generate_sessions_topic(color_enabled));
32 topics.insert("commands".to_string(), Self::generate_commands_topic(color_enabled));
33 topics.insert("modes".to_string(), Self::generate_modes_topic(color_enabled));
34 topics.insert("performance".to_string(), Self::generate_performance_topic(color_enabled));
35 topics.insert("stats".to_string(), Self::generate_stats_topic(color_enabled));
36
37 Self { topics, color_enabled }
38 }
39
40 pub fn show_overview(&self) -> String {
42 let mut output = String::new();
43
44 output.push_str(&self.header("Oxur REPL Help"));
46 output.push('\n');
47
48 output.push_str(&self.section("GETTING STARTED"));
50 output.push_str("Type any Lisp expression at the prompt to evaluate it:\n");
51 output.push_str(&self.example(" (+ 1 2)", Some("3")));
52 output.push_str(&self.example(" (* 6 7)", Some("42")));
53 output.push_str(&self.example(" (println! \"Hi\")", Some("prints and returns ()")));
54 output.push('\n');
55
56 output.push_str(&self.section("CONTROL COMMANDS"));
58 output.push_str(&self.command("(banner)", "Redisplay the welcome banner"));
59 output.push_str(&self.command("(clear)", "Clear the terminal screen"));
60 output.push_str(&self.command("(help)", "Show this help overview"));
61 output.push_str(&self.command("(help <topic>)", "Show detailed help for a topic"));
62 output.push_str(&self.command("(info)", "Show system metadata (versions, platform, etc.)"));
63 output.push_str(&self.command("(quit), (q), (exit)", "Exit the REPL"));
64 output.push_str(&self.command("(stats)", "Show session statistics"));
65 output.push_str(&self.command(
66 "(stats <view>)",
67 "Show specific stats view (views: execution, cache, resources, server, subprocess)",
68 ));
69 output.push('\n');
70
71 output.push_str(&self.section("SESSION MANAGEMENT"));
73 output.push_str(&self.command("(sessions)", "List all active sessions"));
74 output.push_str(&self.command("(current-session)", "Show current session ID"));
75 output.push_str(&self.command("(new-session)", "Create and switch to a new session"));
76 output.push_str(
77 &self.command("(new-session \"name\")", "Create and switch to a named session"),
78 );
79 output.push_str(&self.command("(switch-session <id>)", "Switch to an existing session"));
80 output.push_str(
81 &self.command("(close-session)", "Close current session (must switch first)"),
82 );
83 output.push_str(&self.command("(close-session <id>)", "Close a specific session"));
84 output.push('\n');
85 output.push_str(&self.command("Ctrl-C", "Cancel current input line"));
86 output.push_str(&self.command("Ctrl-D", "Exit the REPL (EOF)"));
87 output.push('\n');
88
89 output.push_str(&self.section("KEYBOARD SHORTCUTS"));
91 output.push_str(&self.command("↑ / ↓", "Navigate command history"));
92 output.push_str(&self.command("Ctrl-A", "Move to start of line"));
93 output.push_str(&self.command("Ctrl-E", "Move to end of line"));
94 output.push_str(&self.command("Ctrl-K", "Kill text from cursor to end"));
95 output.push_str(&self.command("Ctrl-U", "Kill text from start to cursor"));
96 output.push_str(&self.command("Ctrl-R", "Search history (incremental)"));
97 output.push('\n');
98
99 output.push_str(&self.section("EVALUATION"));
101 output.push_str("Oxur uses three-tier execution for optimal performance:\n");
102 output.push_str(&self.command("Tier 1: Calculator", "(~1ms) - Simple arithmetic"));
103 output.push_str(&self.command("Tier 2: Cached JIT", "(~1-5ms) - Previously compiled"));
104 output.push_str(&self.command("Tier 3: Fresh JIT", "(~50-300ms) - New compilation"));
105 output.push('\n');
106 output.push_str(&self.note("⚡ TIP: Repeated evaluations are cached for speed!"));
107 output.push('\n');
108
109 output.push_str(&self.section("HELP TOPICS"));
111 output.push_str(&self.command("basics", "Getting started with Oxur"));
112 output.push_str(&self.command("evaluation", "How evaluation works in detail"));
113 output.push_str(&self.command("keyboard", "All keyboard shortcuts and editing"));
114 output.push_str(&self.command("sessions", "Session management and modes"));
115 output.push_str(&self.command("commands", "All special commands reference"));
116 output.push_str(&self.command("modes", "Lisp vs Sexpr evaluation modes"));
117 output.push_str(&self.command("performance", "Understanding execution tiers"));
118 output.push_str(&self.command("stats", "Statistics and instrumentation"));
119 output.push('\n');
120 output.push_str("Type ");
121 output.push_str(&self.cmd_text("(help <topic>)"));
122 output.push_str(" for detailed information on any topic.\n");
123 output.push('\n');
124 output.push_str("For more information, visit: ");
125 output.push_str(&self.cmd_text("http://oxur.li/"));
126 output.push('\n');
127
128 output
129 }
130
131 pub fn show_topic(&self, topic: &str) -> Option<String> {
135 self.topics.get(topic).cloned()
136 }
137
138 #[allow(dead_code)]
140 pub fn list_topics(&self) -> Vec<&str> {
141 let mut topics: Vec<&str> = self.topics.keys().map(|s| s.as_str()).collect();
142 topics.sort_unstable();
143 topics
144 }
145
146 fn header(&self, text: &str) -> String {
152 let width = text.len() + 4;
153 let top = format!("╭─{}─╮\n", "─".repeat(width - 2));
154 let middle = format!("│ {}{}{} │\n", self.bold_cyan(), text, self.reset());
155 let bottom = format!("╰─{}─╯\n", "─".repeat(width - 2));
156 format!("{}{}{}", top, middle, bottom)
157 }
158
159 fn section(&self, title: &str) -> String {
161 format!("{}{}{}\n{}\n", self.bold_cyan(), title, self.reset(), self.divider())
162 }
163
164 fn command(&self, cmd: &str, desc: &str) -> String {
166 format!(" {}{:<20}{} {}\n", self.yellow(), cmd, self.reset(), desc)
167 }
168
169 fn example(&self, code: &str, result: Option<&str>) -> String {
171 if let Some(res) = result {
172 format!("{}{}{} → {}\n", self.green(), code, self.reset(), res)
173 } else {
174 format!("{}{}{}\n", self.green(), code, self.reset())
175 }
176 }
177
178 fn cmd_text(&self, text: &str) -> String {
180 format!("{}{}{}", self.yellow(), text, self.reset())
181 }
182
183 fn divider(&self) -> String {
185 format!("{}{}{}\n", self.dim(), "─".repeat(60), self.reset())
186 }
187
188 fn note(&self, text: &str) -> String {
190 format!("{}{}{}\n", self.italic_cyan(), text, self.reset())
191 }
192
193 fn bold_cyan(&self) -> &str {
198 if self.color_enabled {
199 "\x1b[1;36m"
200 } else {
201 ""
202 }
203 }
204
205 fn yellow(&self) -> &str {
206 if self.color_enabled {
207 "\x1b[1;33m"
208 } else {
209 ""
210 }
211 }
212
213 fn green(&self) -> &str {
214 if self.color_enabled {
215 "\x1b[32m"
216 } else {
217 ""
218 }
219 }
220
221 fn italic_cyan(&self) -> &str {
222 if self.color_enabled {
223 "\x1b[3;36m"
224 } else {
225 ""
226 }
227 }
228
229 fn dim(&self) -> &str {
230 if self.color_enabled {
231 "\x1b[2;37m"
232 } else {
233 ""
234 }
235 }
236
237 fn reset(&self) -> &str {
238 if self.color_enabled {
239 "\x1b[0m"
240 } else {
241 ""
242 }
243 }
244
245 fn generate_basics_topic(color_enabled: bool) -> String {
250 let help = Self { topics: HashMap::new(), color_enabled };
251 let mut output = String::new();
252
253 output.push_str(&help.header("Getting Started with Oxur"));
254 output.push('\n');
255
256 output.push_str(&help.section("OVERVIEW"));
257 output.push_str("Oxur is a Lisp dialect that compiles to Rust. The REPL provides an\n");
258 output.push_str("interactive environment for evaluating Oxur expressions and seeing\n");
259 output.push_str("immediate results.\n\n");
260
261 output.push_str(&help.section("FIRST STEPS"));
262 output.push_str("Start by trying simple arithmetic:\n\n");
263 output.push_str(&help.example(" (+ 1 2)", Some("3")));
264 output.push_str(&help.example(" (* 6 7)", Some("42")));
265 output.push_str(&help.example(" (- 100 25)", Some("75")));
266 output.push('\n');
267
268 output.push_str("You can nest expressions:\n\n");
269 output.push_str(&help.example(" (+ (* 2 3) (/ 10 2))", Some("11")));
270 output.push('\n');
271
272 output.push_str(&help.section("DEFINING FUNCTIONS"));
273 output.push_str("Define functions with ");
274 output.push_str(&help.cmd_text("deffn"));
275 output.push_str(":\n\n");
276 output.push_str(&help.example(" (deffn square [x] (* x x))", None));
277 output.push_str(&help.example(" (square 5)", Some("25")));
278 output.push('\n');
279
280 output.push_str(&help.section("VARIABLES"));
281 output.push_str("Bind values to names with ");
282 output.push_str(&help.cmd_text("let"));
283 output.push_str(":\n\n");
284 output.push_str(&help.example(" (let x 42)", None));
285 output.push_str(&help.example(" (+ x 8)", Some("50")));
286 output.push('\n');
287
288 output.push_str(&help.note("💡 TIP: Press ↑ to recall previous commands from history"));
289 output.push('\n');
290
291 output.push_str("See also: ");
292 output.push_str(&help.cmd_text("(help evaluation)"));
293 output.push_str(", ");
294 output.push_str(&help.cmd_text("(help modes)"));
295 output.push('\n');
296
297 output
298 }
299
300 fn generate_evaluation_topic(color_enabled: bool) -> String {
301 let help = Self { topics: HashMap::new(), color_enabled };
302 let mut output = String::new();
303
304 output.push_str(&help.header("Evaluation System"));
305 output.push('\n');
306
307 output.push_str(&help.section("OVERVIEW"));
308 output
309 .push_str("Oxur evaluates Lisp expressions by compiling them to Rust and executing\n");
310 output.push_str(
311 "the generated code. To maximize performance, it uses a three-tier system.\n\n",
312 );
313
314 output.push_str(&help.section("THREE-TIER EXECUTION"));
315 output.push('\n');
316
317 output.push_str(&help.cmd_text("Tier 1: Calculator (~1ms)"));
318 output.push('\n');
319 output.push_str(&help.divider());
320 output.push_str("Simple arithmetic and basic operations are evaluated directly\n");
321 output.push_str("without compilation. This is the fastest path.\n\n");
322 output.push_str("Examples:\n");
323 output.push_str(&help.example(" (+ 1 2)", Some("Direct calculation")));
324 output.push_str(&help.example(" (* 10 20)", Some("Direct calculation")));
325 output.push('\n');
326
327 output.push_str(&help.cmd_text("Tier 2: Cached JIT (~1-5ms)"));
328 output.push('\n');
329 output.push_str(&help.divider());
330 output.push_str("Previously compiled expressions are cached in memory. When you\n");
331 output.push_str("evaluate the same expression again, the cached version is used.\n\n");
332 output.push_str("Example:\n");
333 output
334 .push_str(&help.example(" (deffn square [x] (* x x))", Some("Compiles once (~50ms)")));
335 output.push_str(&help.example(" (square 5)", Some("Uses cache (~2ms)")));
336 output.push_str(&help.example(" (square 10)", Some("Uses cache (~2ms)")));
337 output.push('\n');
338
339 output.push_str(&help.cmd_text("Tier 3: Fresh JIT (~50-300ms)"));
340 output.push('\n');
341 output.push_str(&help.divider());
342 output.push_str("New expressions are compiled on-the-fly. The first evaluation\n");
343 output.push_str("is slower but subsequent calls use the cache.\n\n");
344
345 output.push_str(
346 &help.note("⚡ TIP: The REPL shows you which tier was used for each evaluation"),
347 );
348 output.push('\n');
349
350 output.push_str("See also: ");
351 output.push_str(&help.cmd_text("(help performance)"));
352 output.push('\n');
353
354 output
355 }
356
357 fn generate_keyboard_topic(color_enabled: bool) -> String {
358 let help = Self { topics: HashMap::new(), color_enabled };
359 let mut output = String::new();
360
361 output.push_str(&help.header("Keyboard Shortcuts"));
362 output.push('\n');
363
364 output.push_str(&help.section("NAVIGATION"));
365 output.push_str(&help.command("← →", "Move cursor left/right by character"));
366 output.push_str(&help.command("Ctrl-A", "Move to start of line"));
367 output.push_str(&help.command("Ctrl-E", "Move to end of line"));
368 output.push_str(&help.command("Alt-B", "Move backward by word"));
369 output.push_str(&help.command("Alt-F", "Move forward by word"));
370 output.push('\n');
371
372 output.push_str(&help.section("EDITING"));
373 output.push_str(&help.command("Ctrl-K", "Kill text from cursor to end of line"));
374 output.push_str(&help.command("Ctrl-U", "Kill text from start to cursor"));
375 output.push_str(&help.command("Ctrl-W", "Kill word before cursor"));
376 output.push_str(&help.command("Alt-D", "Kill word after cursor"));
377 output.push_str(&help.command("Ctrl-Y", "Yank (paste) killed text"));
378 output.push_str(&help.command("Ctrl-_", "Undo last edit"));
379 output.push('\n');
380
381 output.push_str(&help.section("HISTORY"));
382 output.push_str(&help.command("↑ ↓", "Navigate command history"));
383 output.push_str(&help.command("Ctrl-R", "Search history (incremental search)"));
384 output.push_str(&help.command("Ctrl-G", "Cancel incremental search"));
385 output.push('\n');
386
387 output.push_str(&help.section("CONTROL"));
388 output.push_str(&help.command("Ctrl-C", "Cancel current input line"));
389 output.push_str(&help.command("Ctrl-D", "Exit REPL (if line is empty)"));
390 output.push_str(&help.command("Ctrl-L", "Clear screen"));
391 output.push_str(&help.command("Enter", "Submit current line for evaluation"));
392 output.push('\n');
393
394 output.push_str(&help.note("💡 TIP: History is saved between REPL sessions"));
395 output.push('\n');
396
397 output
398 }
399
400 fn generate_sessions_topic(color_enabled: bool) -> String {
401 let help = Self { topics: HashMap::new(), color_enabled };
402 let mut output = String::new();
403
404 output.push_str(&help.header("Session Management"));
405 output.push('\n');
406
407 output.push_str(&help.section("OVERVIEW"));
408 output.push_str("A session represents an isolated evaluation environment with its\n");
409 output.push_str("own state, variables, and compilation cache. Each REPL connection\n");
410 output.push_str("creates a new session automatically.\n\n");
411
412 output.push_str(&help.section("SESSION MODES"));
413 output.push_str("Sessions can operate in two modes:\n\n");
414
415 output.push_str(&help.cmd_text("Lisp Mode (default)"));
416 output.push('\n');
417 output.push_str(&help.divider());
418 output.push_str("Full Lisp syntax with reader macros and syntactic sugar.\n");
419 output.push_str("This is the standard interactive mode.\n\n");
420 output.push_str("Examples: ");
421 output.push_str(&help.cmd_text("(+ 1 2)"));
422 output.push_str(", ");
423 output.push_str(&help.cmd_text("[1 2 3]"));
424 output.push_str(", ");
425 output.push_str(&help.cmd_text("{:key \"value\"}"));
426 output.push_str("\n\n");
427
428 output.push_str(&help.cmd_text("Sexpr Mode"));
429 output.push('\n');
430 output.push_str(&help.divider());
431 output.push_str("Raw S-expressions in canonical form (what Lisp expands to).\n");
432 output.push_str("Used for debugging the compiler or working with the AST directly.\n\n");
433 output.push_str("Example: ");
434 output.push_str(&help.cmd_text("(Add (Lit 1) (Lit 2))"));
435 output.push_str("\n\n");
436
437 output.push_str(&help.section("MULTIPLE SESSIONS"));
438 output.push_str("You can create and manage multiple sessions within a single REPL.\n");
439 output.push_str("Each session has its own isolated evaluation environment.\n\n");
440
441 output.push_str(&help.section("SESSION COMMANDS"));
442 output.push_str(&help.command("(sessions)", "List all active sessions"));
443 output.push_str(&help.command("(current-session)", "Show current session ID"));
444 output.push_str(&help.command("(new-session)", "Create unnamed session and switch to it"));
445 output.push_str(
446 &help.command("(new-session \"name\")", "Create named session and switch to it"),
447 );
448 output.push_str(&help.command("(switch-session <id>)", "Switch to existing session by ID"));
449 output.push_str(
450 &help.command("(close-session)", "Close current session (must switch first)"),
451 );
452 output.push_str(&help.command("(close-session <id>)", "Close specific session by ID"));
453 output.push('\n');
454
455 output.push_str(&help.section("WORKFLOW EXAMPLE"));
456 output
457 .push_str(&help.example(" (current-session)", Some("Shows your current session ID")));
458 output.push_str(&help.example(
459 " (new-session \"experiment\")",
460 Some("Creates and switches to new session"),
461 ));
462 output
463 .push_str(&help.example(" (deffn foo [] 42)", Some("Define function in new session")));
464 output.push_str(&help.example(" (sessions)", Some("List all sessions")));
465 output.push_str(
466 &help.example(" (switch-session session-abc123)", Some("Switch back to original")),
467 );
468 output.push_str(
469 &help.example(" (close-session experiment-xyz)", Some("Close the experiment session")),
470 );
471 output.push('\n');
472
473 output.push_str(&help.note("💡 TIP: Session state persists until you close it"));
474 output.push('\n');
475 output.push_str(
476 &help.note("⚠️ NOTE: Cannot close the currently active session - switch first"),
477 );
478 output.push('\n');
479
480 output.push_str("See also: ");
481 output.push_str(&help.cmd_text("(help modes)"));
482 output.push('\n');
483
484 output
485 }
486
487 fn generate_commands_topic(color_enabled: bool) -> String {
488 let help = Self { topics: HashMap::new(), color_enabled };
489 let mut output = String::new();
490
491 output.push_str(&help.header("Special Commands Reference"));
492 output.push('\n');
493
494 output.push_str(&help.section("REPL CONTROL"));
495 output.push_str(&help.command("(banner)", "Redisplay the welcome banner"));
496 output.push_str(&help.command("(clear)", "Clear the terminal screen"));
497 output.push_str(&help.command("(exit)", "Exit the REPL (alias for quit)"));
498 output.push_str(&help.command("(help)", "Show help overview"));
499 output.push_str(&help.command("(help <topic>)", "Show detailed help for a specific topic"));
500 output.push_str(
501 &help.command("(info)", "Show system information (versions, platform, etc.)"),
502 );
503 output.push_str(&help.command("(q)", "Exit the REPL (short alias for quit)"));
504 output.push_str(&help.command("(quit)", "Exit the REPL gracefully"));
505 output.push('\n');
506
507 output.push_str(&help.section("SESSION MANAGEMENT"));
508 output.push_str(&help.command("(sessions)", "List all active sessions with metadata"));
509 output.push_str(&help.command("(current-session)", "Display current session ID"));
510 output.push_str(&help.command("(new-session)", "Create new unnamed session"));
511 output.push_str(&help.command("(new-session \"name\")", "Create new named session"));
512 output.push_str(&help.command("(switch-session <id>)", "Switch to existing session"));
513 output.push_str(&help.command("(close-session)", "Close current session (switch first)"));
514 output.push_str(&help.command("(close-session <id>)", "Close specific session by ID"));
515 output.push('\n');
516
517 output.push_str(&help.section("STATISTICS"));
518 output.push_str(&help.command("(stats)", "Show session summary (tiers, cache hit rate)"));
519 output
520 .push_str(&help.command("(stats execution)", "Show detailed tier performance metrics"));
521 output.push_str(&help.command("(stats cache)", "Show cache hit/miss statistics"));
522 output.push_str(&help.command("(stats resources)", "Show memory, disk, and file usage"));
523 output.push_str(&help.command("(stats server)", "Show server metrics (connect mode only)"));
524 output.push_str(&help.command("(stats subprocess)", "Show subprocess lifecycle stats"));
525 output.push('\n');
526
527 output.push_str(&help.section("KEYBOARD SHORTCUTS"));
528 output.push_str(&help.command("Ctrl-C", "Cancel current input and start fresh"));
529 output.push_str(&help.command("Ctrl-D", "Exit REPL if line is empty"));
530 output.push_str(&help.command("Ctrl-L", "Clear the screen"));
531 output.push('\n');
532
533 output.push_str(&help.section("EVALUATION"));
534 output.push_str("Any expression not matching a special command is evaluated\n");
535 output.push_str("as Oxur code using the current session's evaluation mode.\n\n");
536
537 output
538 .push_str(&help.note(
539 "💡 TIP: Special commands are handled locally and don't require compilation",
540 ));
541 output.push('\n');
542
543 output.push_str("See also: ");
544 output.push_str(&help.cmd_text("(help keyboard)"));
545 output.push_str(", ");
546 output.push_str(&help.cmd_text("(help evaluation)"));
547 output.push_str(", ");
548 output.push_str(&help.cmd_text("(help stats)"));
549 output.push('\n');
550
551 output
552 }
553
554 fn generate_modes_topic(color_enabled: bool) -> String {
555 let help = Self { topics: HashMap::new(), color_enabled };
556 let mut output = String::new();
557
558 output.push_str(&help.header("Evaluation Modes"));
559 output.push('\n');
560
561 output.push_str(&help.section("OVERVIEW"));
562 output.push_str("Oxur supports two evaluation modes: Lisp (user-friendly) and\n");
563 output.push_str("Sexpr (canonical form). The mode is set when starting the REPL.\n\n");
564
565 output.push_str(&help.section("LISP MODE (DEFAULT)"));
566 output.push_str("Full Lisp syntax with reader macros, syntactic sugar, and\n");
567 output.push_str("familiar Lisp idioms. This is the recommended mode for\n");
568 output.push_str("interactive development.\n\n");
569
570 output.push_str("Features:\n");
571 output.push_str(" • Reader macros: ");
572 output.push_str(&help.cmd_text("'expr"));
573 output.push_str(" → ");
574 output.push_str(&help.cmd_text("(quote expr)"));
575 output.push('\n');
576 output.push_str(" • Vector literals: ");
577 output.push_str(&help.cmd_text("[1 2 3]"));
578 output.push('\n');
579 output.push_str(" • Map literals: ");
580 output.push_str(&help.cmd_text("{:key \"value\"}"));
581 output.push('\n');
582 output.push_str(" • Set literals: ");
583 output.push_str(&help.cmd_text("#{1 2 3}"));
584 output.push_str("\n\n");
585
586 output.push_str(&help.section("SEXPR MODE"));
587 output.push_str("Raw S-expressions in canonical form. This mode shows you\n");
588 output.push_str("exactly what the Lisp reader produces after expansion.\n\n");
589
590 output.push_str("Use cases:\n");
591 output.push_str(" • Debugging macro expansions\n");
592 output.push_str(" • Understanding compiler internals\n");
593 output.push_str(" • Working directly with the AST\n");
594 output.push_str(" • Testing parser/expander behavior\n\n");
595
596 output.push_str(&help.section("SWITCHING MODES"));
597 output.push_str("The mode is set when launching the REPL:\n\n");
598 output.push_str(&help.example(" oxur repl", Some("Starts in Lisp mode")));
599 output.push_str(&help.example(" oxur repl --mode sexpr", Some("Starts in Sexpr mode")));
600 output.push('\n');
601
602 output.push_str(&help.note("💡 TIP: Most users should use Lisp mode for interactive work"));
603 output.push('\n');
604
605 output.push_str("See also: ");
606 output.push_str(&help.cmd_text("(help sessions)"));
607 output.push('\n');
608
609 output
610 }
611
612 fn generate_performance_topic(color_enabled: bool) -> String {
613 let help = Self { topics: HashMap::new(), color_enabled };
614 let mut output = String::new();
615
616 output.push_str(&help.header("Performance and Optimization"));
617 output.push('\n');
618
619 output.push_str(&help.section("EXECUTION TIERS"));
620 output.push_str("Oxur uses a three-tier execution strategy to balance\n");
621 output.push_str("startup time, throughput, and interactive responsiveness.\n\n");
622
623 output.push_str(&help.cmd_text("Tier 1: Calculator (~1ms)"));
624 output.push('\n');
625 output.push_str(&help.divider());
626 output.push_str("Eligible expressions:\n");
627 output.push_str(" • Pure arithmetic: (+, -, *, /)\n");
628 output.push_str(" • Literal values only (no variables)\n");
629 output.push_str(" • Nested arithmetic operations\n\n");
630 output.push_str("Performance: Sub-millisecond, no compilation overhead\n\n");
631
632 output.push_str(&help.cmd_text("Tier 2: Cached JIT (~1-5ms)"));
633 output.push('\n');
634 output.push_str(&help.divider());
635 output.push_str("Caching strategy:\n");
636 output.push_str(" • Content-addressed: Same code → same cache key\n");
637 output.push_str(" • Session-local: Each session has its own cache\n");
638 output.push_str(" • Persistent: Cache survives across evaluations\n\n");
639 output.push_str("Performance: Near-instant for cached code\n\n");
640
641 output.push_str(&help.cmd_text("Tier 3: Fresh JIT (~50-300ms)"));
642 output.push('\n');
643 output.push_str(&help.divider());
644 output.push_str("Compilation pipeline:\n");
645 output.push_str(" 1. Parse Oxur → AST\n");
646 output.push_str(" 2. Expand macros → Core forms\n");
647 output.push_str(" 3. Lower → Rust AST\n");
648 output.push_str(" 4. Codegen → Rust source\n");
649 output.push_str(" 5. Compile → Dynamic library\n");
650 output.push_str(" 6. Load → Execute\n\n");
651 output.push_str("Performance: One-time cost, then cached\n\n");
652
653 output.push_str(&help.section("OPTIMIZATION TIPS"));
654 output.push_str("⚡ Reuse code: Repeated evaluations use cached compilation\n");
655 output.push_str("⚡ Simple math: Use calculator tier when possible\n");
656 output.push_str("⚡ Define functions: Compiled once, called many times\n");
657 output.push_str("⚡ Batch work: Define multiple functions, then use them\n\n");
658
659 output.push_str(
660 &help.note("💡 TIP: Watch the execution tier in output to understand performance"),
661 );
662 output.push('\n');
663
664 output.push_str("See also: ");
665 output.push_str(&help.cmd_text("(help evaluation)"));
666 output.push('\n');
667
668 output
669 }
670
671 fn generate_stats_topic(color_enabled: bool) -> String {
672 let help = Self { topics: HashMap::new(), color_enabled };
673 let mut output = String::new();
674
675 output.push_str(&help.header("Statistics and Instrumentation"));
676 output.push('\n');
677
678 output.push_str(&help.section("OVERVIEW"));
679 output.push_str("The REPL provides comprehensive statistics and instrumentation to help\n");
680 output.push_str("you understand performance, resource usage, and execution patterns.\n");
681 output.push_str("All statistics use styled tables for clear presentation.\n\n");
682
683 output.push_str(&help.section("AVAILABLE VIEWS"));
684 output.push('\n');
685
686 output.push_str(&help.cmd_text("(stats)"));
687 output.push_str(" - Session Summary\n");
688 output.push_str(&help.divider());
689 output.push_str("Shows a high-level overview of your REPL session:\n");
690 output.push_str(" • Total evaluations performed\n");
691 output.push_str(" • Cache hit rate (hits, misses, percentage)\n");
692 output.push_str(" • Execution tier summary with percentiles (p50, p95, p99)\n\n");
693 output.push_str("Use this view to get a quick snapshot of session performance.\n\n");
694
695 output.push_str(&help.cmd_text("(stats execution)"));
696 output.push_str(" - Detailed Tier Breakdown\n");
697 output.push_str(&help.divider());
698 output.push_str("Shows detailed performance metrics for each execution tier:\n");
699 output.push_str(" • Count: Number of evaluations at this tier\n");
700 output.push_str(" • Min: Fastest evaluation time\n");
701 output.push_str(" • p50 (median): Typical evaluation time\n");
702 output.push_str(" • p95: 95th percentile (outliers excluded)\n");
703 output.push_str(" • p99: 99th percentile (almost all samples)\n");
704 output.push_str(" • Max: Slowest evaluation time\n\n");
705 output.push_str("Use this view to understand tier-specific performance patterns.\n\n");
706
707 output.push_str(&help.cmd_text("(stats cache)"));
708 output.push_str(" - Cache Metrics\n");
709 output.push_str(&help.divider());
710 output.push_str("Shows caching effectiveness:\n");
711 output.push_str(" • Hits: Number of cache hits (fast path)\n");
712 output.push_str(" • Misses: Number of cache misses (compilation required)\n");
713 output.push_str(" • Hit Rate: Percentage of evaluations served from cache\n\n");
714 output.push_str("A high hit rate (>70%) indicates good code reuse patterns.\n\n");
715
716 output.push_str(&help.cmd_text("(stats resources)"));
717 output.push_str(" - Resource Usage\n");
718 output.push_str(&help.divider());
719 output.push_str("Shows system resource consumption:\n\n");
720 output.push_str("Memory:\n");
721 output.push_str(" • Process RSS: Resident memory (actual RAM used)\n");
722 output.push_str(" • Virtual Memory: Total address space\n");
723 output.push_str(" • Process ID: Operating system PID\n\n");
724 output.push_str("Session Directory:\n");
725 output.push_str(" • Location: Path to session temp directory\n");
726 output.push_str(" • Files: Count of compiled artifacts\n");
727 output.push_str(" • Disk Usage: Total space consumed\n\n");
728 output.push_str("Artifact Cache (Global):\n");
729 output.push_str(" • Entries: Number of cached artifacts\n");
730 output.push_str(" • Total Size: Disk space used by cache\n");
731 output.push_str(" • Oldest Entry: Age of oldest cached item\n");
732 output.push_str(" • Cache Directory: Path to global cache\n\n");
733
734 output.push_str(&help.cmd_text("(stats server)"));
735 output.push_str(" - Server Metrics (Connect Mode Only)\n");
736 output.push_str(&help.divider());
737 output.push_str("Shows server-wide metrics when connected to a remote server:\n\n");
738 output.push_str("Connections:\n");
739 output.push_str(" • Total Connections: All-time connection count\n");
740 output.push_str(" • Active Connections: Currently connected clients\n\n");
741 output.push_str("Sessions:\n");
742 output.push_str(" • Total Sessions: All-time session count\n");
743 output.push_str(" • Active Sessions: Currently running sessions\n\n");
744 output.push_str("Requests & Responses:\n");
745 output.push_str(" • Total Requests/Responses: Message counts\n");
746 output.push_str(" • Successful/Errors: Response status breakdown\n");
747 output.push_str(" • Success Rate: Percentage of successful operations\n\n");
748 output.push_str("Note: Only available in 'connect' mode (remote server).\n");
749 output.push_str("In interactive mode, use 'oxur repl serve' to run a server.\n\n");
750
751 output.push_str(&help.cmd_text("(stats subprocess)"));
752 output.push_str(" - Subprocess Lifecycle\n");
753 output.push_str(&help.divider());
754 output.push_str("Shows subprocess lifecycle metrics (for forked execution):\n\n");
755 output.push_str("Status:\n");
756 output.push_str(" • Status: Running or Stopped\n");
757 output.push_str(" • Uptime: How long the subprocess has been running\n");
758 output.push_str(" • Restart Count: Number of restarts since session start\n");
759 output.push_str(" • Last Restart Reason: Why the subprocess last restarted\n\n");
760 output.push_str("Restart reasons include:\n");
761 output.push_str(" • Clean shutdown: Normal exit (code 0)\n");
762 output.push_str(" • Error exit: Non-zero exit code\n");
763 output.push_str(" • Segfault: Memory access violation (SIGSEGV)\n");
764 output.push_str(" • Killed: Process killed (SIGKILL, possibly OOM)\n");
765 output.push_str(" • Aborted: Assertion failure (SIGABRT)\n");
766 output.push_str(" • User requested: Manual restart command\n\n");
767
768 output.push_str(&help.section("PERCENTILES EXPLAINED"));
769 output.push_str("Percentiles give you a better understanding of typical performance\n");
770 output.push_str("than simple averages:\n\n");
771 output.push_str(" • ");
772 output.push_str(&help.cmd_text("p50 (median)"));
773 output.push_str(": Half of evaluations are faster\n");
774 output.push_str(" • ");
775 output.push_str(&help.cmd_text("p95"));
776 output.push_str(": 95% of evaluations are faster (excludes slow outliers)\n");
777 output.push_str(" • ");
778 output.push_str(&help.cmd_text("p99"));
779 output.push_str(": 99% of evaluations are faster (includes most samples)\n\n");
780 output.push_str("If p50 and p95 are close, performance is consistent.\n");
781 output
782 .push_str("If p99 is much higher than p95, you have occasional slow evaluations.\n\n");
783
784 output.push_str(&help.section("UNDERSTANDING THE TIERS"));
785 output.push_str("The stats show performance across three execution tiers:\n\n");
786 output.push_str(&help.cmd_text("Calculator"));
787 output.push_str(": Simple arithmetic (~1ms)\n");
788 output.push_str(" Fast, no compilation, direct evaluation\n\n");
789 output.push_str(&help.cmd_text("Cached"));
790 output.push_str(": Previously compiled (~1-5ms)\n");
791 output.push_str(" Near-instant, uses cached compilation\n\n");
792 output.push_str(&help.cmd_text("JIT"));
793 output.push_str(": Fresh compilation (~50-300ms)\n");
794 output.push_str(" Slower first time, then cached\n\n");
795
796 output.push_str(&help.section("OPTIMIZATION TIPS"));
797 output.push_str("Use stats to guide optimization:\n\n");
798 output.push_str("⚡ Low cache hit rate? Reuse more code patterns\n");
799 output.push_str("⚡ High p99 on Calculator tier? Simplify expressions\n");
800 output.push_str("⚡ Many JIT evaluations? Define functions for reuse\n");
801 output.push_str("⚡ High memory usage? Consider restarting REPL session\n\n");
802
803 output.push_str(&help.note("💡 TIP: Track stats over time to see performance trends"));
804 output.push('\n');
805
806 output.push_str("See also: ");
807 output.push_str(&help.cmd_text("(help performance)"));
808 output.push_str(", ");
809 output.push_str(&help.cmd_text("(help evaluation)"));
810 output.push('\n');
811
812 output
813 }
814}
815
816#[cfg(test)]
817mod tests {
818 use super::*;
819
820 #[test]
821 fn test_help_system_creation() {
822 let help = HelpSystem::new(true);
823 assert_eq!(help.list_topics().len(), 8);
824 }
825
826 #[test]
827 fn test_overview_has_all_sections() {
828 let help = HelpSystem::new(false);
829 let overview = help.show_overview();
830 assert!(overview.contains("GETTING STARTED"));
831 assert!(overview.contains("CONTROL COMMANDS"));
832 assert!(overview.contains("KEYBOARD SHORTCUTS"));
833 assert!(overview.contains("EVALUATION"));
834 assert!(overview.contains("HELP TOPICS"));
835 }
836
837 #[test]
838 fn test_all_topics_available() {
839 let help = HelpSystem::new(false);
840 let topics = vec![
841 "basics",
842 "evaluation",
843 "keyboard",
844 "sessions",
845 "commands",
846 "modes",
847 "performance",
848 "stats",
849 ];
850 for topic in topics {
851 assert!(help.show_topic(topic).is_some(), "Topic {} should exist", topic);
852 }
853 }
854
855 #[test]
856 fn test_color_disabled() {
857 let help = HelpSystem::new(false);
858 let overview = help.show_overview();
859 assert!(!overview.contains("\x1b["), "Should not contain ANSI codes");
860 }
861
862 #[test]
863 fn test_color_enabled() {
864 let help = HelpSystem::new(true);
865 let overview = help.show_overview();
866 assert!(overview.contains("\x1b["), "Should contain ANSI codes");
867 }
868
869 #[test]
870 fn test_unknown_topic() {
871 let help = HelpSystem::new(false);
872 assert!(help.show_topic("nonexistent").is_none());
873 }
874
875 #[test]
876 fn test_topics_list_is_sorted() {
877 let help = HelpSystem::new(false);
878 let topics = help.list_topics();
879 let mut sorted_topics = topics.clone();
880 sorted_topics.sort_unstable();
881 assert_eq!(topics, sorted_topics);
882 }
883
884 #[test]
885 fn test_each_topic_has_header() {
886 let help = HelpSystem::new(false);
887 for topic in help.list_topics() {
888 let content = help.show_topic(topic).unwrap();
889 assert!(content.contains("╭─"), "Topic {} should have header", topic);
890 assert!(content.contains("╰─"), "Topic {} should have header", topic);
891 }
892 }
893
894 #[test]
895 fn test_overview_mentions_all_topics() {
896 let help = HelpSystem::new(false);
897 let overview = help.show_overview();
898 for topic in help.list_topics() {
899 assert!(overview.contains(topic), "Overview should mention topic {}", topic);
900 }
901 }
902}