1use std::collections::HashMap;
26use std::path::PathBuf;
27use std::sync::Arc;
28
29use anyhow::{Context, Result};
30use tokio::sync::RwLock;
31
32use async_trait::async_trait;
33
34use crate::ast::{Arg, Command, Expr, Stmt, StringPart, ToolDef, Value, BinaryOp};
35use crate::backend::{BackendError, KernelBackend};
36use kaish_glob::glob_match;
37use crate::dispatch::{CommandDispatcher, PipelinePosition};
38use crate::interpreter::{apply_output_format, eval_expr, expand_tilde, json_to_value, value_to_string, ControlFlow, ExecResult, Scope};
39use crate::parser::parse;
40use crate::scheduler::{drain_to_stream, is_bool_type, schema_param_lookup, stderr_stream, BoundedStream, JobManager, PipelineRunner, StderrReceiver, DEFAULT_STREAM_MAX_SIZE};
41use crate::tools::{extract_output_format, register_builtins, resolve_in_path, ExecContext, ToolArgs, ToolRegistry};
42use crate::validator::{Severity, Validator};
43use crate::vfs::{BuiltinFs, JobFs, LocalFs, MemoryFs, VfsRouter};
44
45#[derive(Debug, Clone)]
52pub enum VfsMountMode {
53 Passthrough,
62
63 Sandboxed {
78 root: Option<PathBuf>,
81 },
82
83 NoLocal,
93}
94
95impl Default for VfsMountMode {
96 fn default() -> Self {
97 VfsMountMode::Sandboxed { root: None }
98 }
99}
100
101#[derive(Debug, Clone)]
103pub struct KernelConfig {
104 pub name: String,
106
107 pub vfs_mode: VfsMountMode,
109
110 pub cwd: PathBuf,
112
113 pub skip_validation: bool,
119
120 pub interactive: bool,
125
126 pub ignore_config: crate::ignore_config::IgnoreConfig,
128
129 pub output_limit: crate::output_limit::OutputLimitConfig,
131
132 pub allow_external_commands: bool,
142
143 pub latch_enabled: bool,
148
149 pub trash_enabled: bool,
155
156 pub nonce_store: Option<crate::nonce::NonceStore>,
162}
163
164fn default_sandbox_root() -> PathBuf {
166 std::env::var("HOME")
167 .map(PathBuf::from)
168 .unwrap_or_else(|_| PathBuf::from("/"))
169}
170
171impl Default for KernelConfig {
172 fn default() -> Self {
173 let home = default_sandbox_root();
174 Self {
175 name: "default".to_string(),
176 vfs_mode: VfsMountMode::Sandboxed { root: None },
177 cwd: home,
178 skip_validation: false,
179 interactive: false,
180 ignore_config: crate::ignore_config::IgnoreConfig::none(),
181 output_limit: crate::output_limit::OutputLimitConfig::none(),
182 allow_external_commands: true,
183 latch_enabled: std::env::var("KAISH_LATCH").is_ok_and(|v| v == "1"),
184 trash_enabled: std::env::var("KAISH_TRASH").is_ok_and(|v| v == "1"),
185 nonce_store: None,
186 }
187 }
188}
189
190impl KernelConfig {
191 pub fn transient() -> Self {
193 let home = default_sandbox_root();
194 Self {
195 name: "transient".to_string(),
196 vfs_mode: VfsMountMode::Sandboxed { root: None },
197 cwd: home,
198 skip_validation: false,
199 interactive: false,
200 ignore_config: crate::ignore_config::IgnoreConfig::none(),
201 output_limit: crate::output_limit::OutputLimitConfig::none(),
202 allow_external_commands: true,
203 latch_enabled: false,
204 trash_enabled: false,
205 nonce_store: None,
206 }
207 }
208
209 pub fn named(name: &str) -> Self {
211 let home = default_sandbox_root();
212 Self {
213 name: name.to_string(),
214 vfs_mode: VfsMountMode::Sandboxed { root: None },
215 cwd: home,
216 skip_validation: false,
217 interactive: false,
218 ignore_config: crate::ignore_config::IgnoreConfig::none(),
219 output_limit: crate::output_limit::OutputLimitConfig::none(),
220 allow_external_commands: true,
221 latch_enabled: false,
222 trash_enabled: false,
223 nonce_store: None,
224 }
225 }
226
227 pub fn repl() -> Self {
232 let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("/"));
233 Self {
234 name: "repl".to_string(),
235 vfs_mode: VfsMountMode::Passthrough,
236 cwd,
237 skip_validation: false,
238 interactive: false,
239 ignore_config: crate::ignore_config::IgnoreConfig::none(),
240 output_limit: crate::output_limit::OutputLimitConfig::none(),
241 allow_external_commands: true,
242 latch_enabled: std::env::var("KAISH_LATCH").is_ok_and(|v| v == "1"),
243 trash_enabled: std::env::var("KAISH_TRASH").is_ok_and(|v| v == "1"),
244 nonce_store: None,
245 }
246 }
247
248 pub fn mcp() -> Self {
255 let home = default_sandbox_root();
256 Self {
257 name: "mcp".to_string(),
258 vfs_mode: VfsMountMode::Sandboxed { root: None },
259 cwd: home,
260 skip_validation: false,
261 interactive: false,
262 ignore_config: crate::ignore_config::IgnoreConfig::mcp(),
263 output_limit: crate::output_limit::OutputLimitConfig::mcp(),
264 allow_external_commands: true,
265 latch_enabled: std::env::var("KAISH_LATCH").is_ok_and(|v| v == "1"),
266 trash_enabled: std::env::var("KAISH_TRASH").is_ok_and(|v| v == "1"),
267 nonce_store: None,
268 }
269 }
270
271 pub fn mcp_with_root(root: PathBuf) -> Self {
275 Self {
276 name: "mcp".to_string(),
277 vfs_mode: VfsMountMode::Sandboxed { root: Some(root.clone()) },
278 cwd: root,
279 skip_validation: false,
280 interactive: false,
281 ignore_config: crate::ignore_config::IgnoreConfig::mcp(),
282 output_limit: crate::output_limit::OutputLimitConfig::mcp(),
283 allow_external_commands: true,
284 latch_enabled: std::env::var("KAISH_LATCH").is_ok_and(|v| v == "1"),
285 trash_enabled: std::env::var("KAISH_TRASH").is_ok_and(|v| v == "1"),
286 nonce_store: None,
287 }
288 }
289
290 pub fn isolated() -> Self {
295 Self {
296 name: "isolated".to_string(),
297 vfs_mode: VfsMountMode::NoLocal,
298 cwd: PathBuf::from("/"),
299 skip_validation: false,
300 interactive: false,
301 ignore_config: crate::ignore_config::IgnoreConfig::none(),
302 output_limit: crate::output_limit::OutputLimitConfig::none(),
303 allow_external_commands: false,
304 latch_enabled: false,
305 trash_enabled: false,
306 nonce_store: None,
307 }
308 }
309
310 pub fn with_vfs_mode(mut self, mode: VfsMountMode) -> Self {
312 self.vfs_mode = mode;
313 self
314 }
315
316 pub fn with_cwd(mut self, cwd: PathBuf) -> Self {
318 self.cwd = cwd;
319 self
320 }
321
322 pub fn with_skip_validation(mut self, skip: bool) -> Self {
324 self.skip_validation = skip;
325 self
326 }
327
328 pub fn with_interactive(mut self, interactive: bool) -> Self {
330 self.interactive = interactive;
331 self
332 }
333
334 pub fn with_ignore_config(mut self, config: crate::ignore_config::IgnoreConfig) -> Self {
336 self.ignore_config = config;
337 self
338 }
339
340 pub fn with_output_limit(mut self, config: crate::output_limit::OutputLimitConfig) -> Self {
342 self.output_limit = config;
343 self
344 }
345
346 pub fn with_allow_external_commands(mut self, allow: bool) -> Self {
352 self.allow_external_commands = allow;
353 self
354 }
355
356 pub fn with_latch(mut self, enabled: bool) -> Self {
358 self.latch_enabled = enabled;
359 self
360 }
361
362 pub fn with_trash(mut self, enabled: bool) -> Self {
364 self.trash_enabled = enabled;
365 self
366 }
367
368 pub fn with_nonce_store(mut self, store: crate::nonce::NonceStore) -> Self {
373 self.nonce_store = Some(store);
374 self
375 }
376}
377
378pub struct Kernel {
383 name: String,
385 scope: RwLock<Scope>,
387 tools: Arc<ToolRegistry>,
389 user_tools: RwLock<HashMap<String, ToolDef>>,
391 vfs: Arc<VfsRouter>,
393 jobs: Arc<JobManager>,
395 runner: PipelineRunner,
397 exec_ctx: RwLock<ExecContext>,
399 skip_validation: bool,
401 interactive: bool,
403 allow_external_commands: bool,
405 stderr_receiver: tokio::sync::Mutex<StderrReceiver>,
410 cancel_token: std::sync::Mutex<tokio_util::sync::CancellationToken>,
416 #[cfg(unix)]
418 terminal_state: Option<Arc<crate::terminal::TerminalState>>,
419}
420
421impl Kernel {
422 pub fn new(config: KernelConfig) -> Result<Self> {
424 let mut vfs = Self::setup_vfs(&config);
425 let jobs = Arc::new(JobManager::new());
426
427 vfs.mount("/v/jobs", JobFs::new(jobs.clone()));
429
430 Self::assemble(config, vfs, jobs, |vfs_ref, tools| {
431 ExecContext::with_vfs_and_tools(vfs_ref.clone(), tools.clone())
432 })
433 }
434
435 fn setup_vfs(config: &KernelConfig) -> VfsRouter {
437 let mut vfs = VfsRouter::new();
438
439 match &config.vfs_mode {
440 VfsMountMode::Passthrough => {
441 vfs.mount("/", LocalFs::new(PathBuf::from("/")));
443 vfs.mount("/v", MemoryFs::new());
445 }
446 VfsMountMode::Sandboxed { root } => {
447 vfs.mount("/", MemoryFs::new());
449 vfs.mount("/v", MemoryFs::new());
450
451 vfs.mount("/tmp", LocalFs::new(PathBuf::from("/tmp")));
453
454 let runtime = crate::paths::xdg_runtime_dir();
456 if runtime.exists() {
457 let runtime_str = runtime.to_string_lossy().to_string();
458 vfs.mount(&runtime_str, LocalFs::new(runtime));
459 }
460
461 let local_root = root.clone().unwrap_or_else(|| {
463 std::env::var("HOME")
464 .map(PathBuf::from)
465 .unwrap_or_else(|_| PathBuf::from("/"))
466 });
467
468 let mount_point = local_root.to_string_lossy().to_string();
472 vfs.mount(&mount_point, LocalFs::new(local_root));
473 }
474 VfsMountMode::NoLocal => {
475 vfs.mount("/", MemoryFs::new());
477 vfs.mount("/tmp", MemoryFs::new());
478 vfs.mount("/v", MemoryFs::new());
479 }
480 }
481
482 vfs
483 }
484
485 pub fn transient() -> Result<Self> {
487 Self::new(KernelConfig::transient())
488 }
489
490 pub fn with_backend(
519 backend: Arc<dyn KernelBackend>,
520 config: KernelConfig,
521 configure_vfs: impl FnOnce(&mut VfsRouter),
522 ) -> Result<Self> {
523 use crate::backend::VirtualOverlayBackend;
524
525 let mut vfs = VfsRouter::new();
526 let jobs = Arc::new(JobManager::new());
527
528 vfs.mount("/v/jobs", JobFs::new(jobs.clone()));
529 vfs.mount("/v/blobs", MemoryFs::new());
530
531 configure_vfs(&mut vfs);
533
534 Self::assemble(config, vfs, jobs, |vfs_arc: &Arc<VfsRouter>, _: &Arc<ToolRegistry>| {
535 let overlay: Arc<dyn KernelBackend> =
536 Arc::new(VirtualOverlayBackend::new(backend, vfs_arc.clone()));
537 ExecContext::with_backend(overlay)
538 })
539 }
540
541 fn assemble(
547 config: KernelConfig,
548 mut vfs: VfsRouter,
549 jobs: Arc<JobManager>,
550 make_ctx: impl FnOnce(&Arc<VfsRouter>, &Arc<ToolRegistry>) -> ExecContext,
551 ) -> Result<Self> {
552 let KernelConfig { name, cwd, skip_validation, interactive, ignore_config, output_limit, allow_external_commands, latch_enabled, trash_enabled, nonce_store, .. } = config;
553
554 let mut tools = ToolRegistry::new();
555 register_builtins(&mut tools);
556 let tools = Arc::new(tools);
557
558 vfs.mount("/v/bin", BuiltinFs::new(tools.clone()));
560
561 let vfs = Arc::new(vfs);
562
563 let runner = PipelineRunner::new(tools.clone());
564
565 let (stderr_writer, stderr_receiver) = stderr_stream();
566
567 let mut exec_ctx = make_ctx(&vfs, &tools);
568 exec_ctx.set_cwd(cwd);
569 exec_ctx.set_job_manager(jobs.clone());
570 exec_ctx.set_tool_schemas(tools.schemas());
571 exec_ctx.set_tools(tools.clone());
572 exec_ctx.stderr = Some(stderr_writer);
573 exec_ctx.ignore_config = ignore_config;
574 exec_ctx.output_limit = output_limit;
575 exec_ctx.allow_external_commands = allow_external_commands;
576 if let Some(store) = nonce_store {
577 exec_ctx.nonce_store = store;
578 }
579
580 Ok(Self {
581 name,
582 scope: RwLock::new({
583 let mut scope = Scope::new();
584 if let Ok(home) = std::env::var("HOME") {
585 scope.set("HOME", Value::String(home));
586 }
587 scope.set_latch_enabled(latch_enabled);
588 scope.set_trash_enabled(trash_enabled);
589 scope
590 }),
591 tools,
592 user_tools: RwLock::new(HashMap::new()),
593 vfs,
594 jobs,
595 runner,
596 exec_ctx: RwLock::new(exec_ctx),
597 skip_validation,
598 interactive,
599 allow_external_commands,
600 stderr_receiver: tokio::sync::Mutex::new(stderr_receiver),
601 cancel_token: std::sync::Mutex::new(tokio_util::sync::CancellationToken::new()),
602 #[cfg(unix)]
603 terminal_state: None,
604 })
605 }
606
607 pub fn name(&self) -> &str {
609 &self.name
610 }
611
612 #[cfg(unix)]
617 pub fn init_terminal(&mut self) {
618 if !self.interactive {
619 return;
620 }
621 match crate::terminal::TerminalState::init() {
622 Ok(state) => {
623 let state = Arc::new(state);
624 self.terminal_state = Some(state.clone());
625 self.exec_ctx.get_mut().terminal_state = Some(state);
627 tracing::debug!("terminal job control initialized");
628 }
629 Err(e) => {
630 tracing::warn!("failed to initialize terminal job control: {}", e);
631 }
632 }
633 }
634
635 pub fn cancel(&self) {
641 #[allow(clippy::expect_used)]
642 let token = self.cancel_token.lock().expect("cancel_token poisoned");
643 token.cancel();
644 }
645
646 pub fn is_cancelled(&self) -> bool {
648 #[allow(clippy::expect_used)]
649 let token = self.cancel_token.lock().expect("cancel_token poisoned");
650 token.is_cancelled()
651 }
652
653 fn reset_cancel(&self) -> tokio_util::sync::CancellationToken {
655 #[allow(clippy::expect_used)]
656 let mut token = self.cancel_token.lock().expect("cancel_token poisoned");
657 if token.is_cancelled() {
658 *token = tokio_util::sync::CancellationToken::new();
659 }
660 token.clone()
661 }
662
663 pub async fn execute(&self, input: &str) -> Result<ExecResult> {
667 self.execute_streaming(input, &mut |_| {}).await
668 }
669
670 #[tracing::instrument(level = "info", skip(self, on_output), fields(input_len = input.len()))]
679 pub async fn execute_streaming(
680 &self,
681 input: &str,
682 on_output: &mut dyn FnMut(&ExecResult),
683 ) -> Result<ExecResult> {
684 let program = parse(input).map_err(|errors| {
685 let msg = errors
686 .iter()
687 .map(|e| e.to_string())
688 .collect::<Vec<_>>()
689 .join("; ");
690 anyhow::anyhow!("parse error: {}", msg)
691 })?;
692
693 {
695 let scope = self.scope.read().await;
696 if scope.show_ast() {
697 let output = format!("{:#?}\n", program);
698 return Ok(ExecResult::with_output(crate::interpreter::OutputData::text(output)));
699 }
700 }
701
702 if !self.skip_validation {
704 let user_tools = self.user_tools.read().await;
705 let validator = Validator::new(&self.tools, &user_tools);
706 let issues = validator.validate(&program);
707
708 let errors: Vec<_> = issues
710 .iter()
711 .filter(|i| i.severity == Severity::Error)
712 .collect();
713
714 if !errors.is_empty() {
715 let error_msg = errors
716 .iter()
717 .map(|e| e.format(input))
718 .collect::<Vec<_>>()
719 .join("\n");
720 return Err(anyhow::anyhow!("validation failed:\n{}", error_msg));
721 }
722
723 for warning in issues.iter().filter(|i| i.severity == Severity::Warning) {
725 tracing::trace!("validation: {}", warning.format(input));
726 }
727 }
728
729 let mut result = ExecResult::success("");
730
731 let cancel = self.reset_cancel();
733
734 for stmt in program.statements {
735 if matches!(stmt, Stmt::Empty) {
736 continue;
737 }
738
739 if cancel.is_cancelled() {
741 result.code = 130;
742 return Ok(result);
743 }
744
745 let flow = self.execute_stmt_flow(&stmt).await?;
746
747 let drained_stderr = {
751 let mut receiver = self.stderr_receiver.lock().await;
752 receiver.drain_lossy()
753 };
754
755 match flow {
756 ControlFlow::Normal(mut r) => {
757 if !drained_stderr.is_empty() {
758 if !r.err.is_empty() && !r.err.ends_with('\n') {
759 r.err.push('\n');
760 }
761 let combined = format!("{}{}", drained_stderr, r.err);
763 r.err = combined;
764 }
765 on_output(&r);
766 let last_output = r.output.clone();
770 accumulate_result(&mut result, &r);
771 result.output = last_output;
772 }
773 ControlFlow::Exit { code } => {
774 if !drained_stderr.is_empty() {
775 result.err.push_str(&drained_stderr);
776 }
777 result.code = code;
778 return Ok(result);
779 }
780 ControlFlow::Return { mut value } => {
781 if !drained_stderr.is_empty() {
782 value.err = format!("{}{}", drained_stderr, value.err);
783 }
784 on_output(&value);
785 result = value;
786 }
787 ControlFlow::Break { result: mut r, .. } | ControlFlow::Continue { result: mut r, .. } => {
788 if !drained_stderr.is_empty() {
789 r.err = format!("{}{}", drained_stderr, r.err);
790 }
791 on_output(&r);
792 result = r;
793 }
794 }
795 }
796
797 Ok(result)
798 }
799
800 fn execute_stmt_flow<'a>(
802 &'a self,
803 stmt: &'a Stmt,
804 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<ControlFlow>> + Send + 'a>> {
805 use tracing::Instrument;
806 let span = tracing::debug_span!("execute_stmt_flow", stmt_type = %stmt.kind_name());
807 Box::pin(async move {
808 match stmt {
809 Stmt::Assignment(assign) => {
810 let value = self.eval_expr_async(&assign.value).await
812 .context("failed to evaluate assignment")?;
813 let mut scope = self.scope.write().await;
814 if assign.local {
815 scope.set(&assign.name, value.clone());
817 } else {
818 scope.set_global(&assign.name, value.clone());
820 }
821 drop(scope);
822
823 Ok(ControlFlow::ok(ExecResult::success("")))
825 }
826 Stmt::Command(cmd) => {
827 let pipeline = crate::ast::Pipeline {
830 commands: vec![cmd.clone()],
831 background: false,
832 };
833 let result = self.execute_pipeline(&pipeline).await?;
834 self.update_last_result(&result).await;
835
836 if !result.ok() {
838 let scope = self.scope.read().await;
839 if scope.error_exit_enabled() {
840 return Ok(ControlFlow::exit_code(result.code));
841 }
842 }
843
844 Ok(ControlFlow::ok(result))
845 }
846 Stmt::Pipeline(pipeline) => {
847 let result = self.execute_pipeline(pipeline).await?;
848 self.update_last_result(&result).await;
849
850 if !result.ok() {
852 let scope = self.scope.read().await;
853 if scope.error_exit_enabled() {
854 return Ok(ControlFlow::exit_code(result.code));
855 }
856 }
857
858 Ok(ControlFlow::ok(result))
859 }
860 Stmt::If(if_stmt) => {
861 let cond_value = self.eval_expr_async(&if_stmt.condition).await?;
863
864 let branch = if is_truthy(&cond_value) {
865 &if_stmt.then_branch
866 } else {
867 if_stmt.else_branch.as_deref().unwrap_or(&[])
868 };
869
870 let mut result = ExecResult::success("");
871 for stmt in branch {
872 let flow = self.execute_stmt_flow(stmt).await?;
873 match flow {
874 ControlFlow::Normal(r) => {
875 accumulate_result(&mut result, &r);
876 self.drain_stderr_into(&mut result).await;
877 }
878 other => {
879 self.drain_stderr_into(&mut result).await;
880 return Ok(other);
881 }
882 }
883 }
884 Ok(ControlFlow::ok(result))
885 }
886 Stmt::For(for_loop) => {
887 let mut items: Vec<Value> = Vec::new();
890 for item_expr in &for_loop.items {
891 let item = self.eval_expr_async(item_expr).await?;
892 match &item {
894 Value::Json(serde_json::Value::Array(arr)) => {
896 for elem in arr {
897 items.push(json_to_value(elem.clone()));
898 }
899 }
900 Value::String(_) => {
903 items.push(item);
904 }
905 _ => items.push(item),
907 }
908 }
909
910 let mut result = ExecResult::success("");
911 {
912 let mut scope = self.scope.write().await;
913 scope.push_frame();
914 }
915
916 'outer: for item in items {
917 if self.is_cancelled() {
919 let mut scope = self.scope.write().await;
920 scope.pop_frame();
921 result.code = 130;
922 return Ok(ControlFlow::ok(result));
923 }
924 {
925 let mut scope = self.scope.write().await;
926 scope.set(&for_loop.variable, item);
927 }
928 for stmt in &for_loop.body {
929 let mut flow = self.execute_stmt_flow(stmt).await?;
930 self.drain_stderr_into(&mut result).await;
931 match &mut flow {
932 ControlFlow::Normal(r) => {
933 accumulate_result(&mut result, r);
934 if !r.ok() {
935 let scope = self.scope.read().await;
936 if scope.error_exit_enabled() {
937 drop(scope);
938 let mut scope = self.scope.write().await;
939 scope.pop_frame();
940 return Ok(ControlFlow::exit_code(r.code));
941 }
942 }
943 }
944 ControlFlow::Break { .. } => {
945 if flow.decrement_level() {
946 break 'outer;
947 }
948 let mut scope = self.scope.write().await;
949 scope.pop_frame();
950 return Ok(flow);
951 }
952 ControlFlow::Continue { .. } => {
953 if flow.decrement_level() {
954 continue 'outer;
955 }
956 let mut scope = self.scope.write().await;
957 scope.pop_frame();
958 return Ok(flow);
959 }
960 ControlFlow::Return { .. } | ControlFlow::Exit { .. } => {
961 let mut scope = self.scope.write().await;
962 scope.pop_frame();
963 return Ok(flow);
964 }
965 }
966 }
967 }
968
969 {
970 let mut scope = self.scope.write().await;
971 scope.pop_frame();
972 }
973 Ok(ControlFlow::ok(result))
974 }
975 Stmt::While(while_loop) => {
976 let mut result = ExecResult::success("");
977
978 'outer: loop {
979 if self.is_cancelled() {
982 result.code = 130;
983 return Ok(ControlFlow::ok(result));
984 }
985
986 let cond_value = self.eval_expr_async(&while_loop.condition).await?;
987
988 if !is_truthy(&cond_value) {
989 break;
990 }
991
992 for stmt in &while_loop.body {
994 let mut flow = self.execute_stmt_flow(stmt).await?;
995 self.drain_stderr_into(&mut result).await;
996 match &mut flow {
997 ControlFlow::Normal(r) => {
998 accumulate_result(&mut result, r);
999 if !r.ok() {
1000 let scope = self.scope.read().await;
1001 if scope.error_exit_enabled() {
1002 return Ok(ControlFlow::exit_code(r.code));
1003 }
1004 }
1005 }
1006 ControlFlow::Break { .. } => {
1007 if flow.decrement_level() {
1008 break 'outer;
1009 }
1010 return Ok(flow);
1011 }
1012 ControlFlow::Continue { .. } => {
1013 if flow.decrement_level() {
1014 continue 'outer;
1015 }
1016 return Ok(flow);
1017 }
1018 ControlFlow::Return { .. } | ControlFlow::Exit { .. } => {
1019 return Ok(flow);
1020 }
1021 }
1022 }
1023 }
1024
1025 Ok(ControlFlow::ok(result))
1026 }
1027 Stmt::Case(case_stmt) => {
1028 let match_value = {
1030 let mut scope = self.scope.write().await;
1031 let value = eval_expr(&case_stmt.expr, &mut scope)?;
1032 value_to_string(&value)
1033 };
1034
1035 for branch in &case_stmt.branches {
1037 let matched = branch.patterns.iter().any(|pattern| {
1038 glob_match(pattern, &match_value)
1039 });
1040
1041 if matched {
1042 let mut result = ExecResult::success("");
1044 for stmt in &branch.body {
1045 let flow = self.execute_stmt_flow(stmt).await?;
1046 match flow {
1047 ControlFlow::Normal(r) => {
1048 accumulate_result(&mut result, &r);
1049 self.drain_stderr_into(&mut result).await;
1050 }
1051 other => {
1052 self.drain_stderr_into(&mut result).await;
1053 return Ok(other);
1054 }
1055 }
1056 }
1057 return Ok(ControlFlow::ok(result));
1058 }
1059 }
1060
1061 Ok(ControlFlow::ok(ExecResult::success("")))
1063 }
1064 Stmt::Break(levels) => {
1065 Ok(ControlFlow::break_n(levels.unwrap_or(1)))
1066 }
1067 Stmt::Continue(levels) => {
1068 Ok(ControlFlow::continue_n(levels.unwrap_or(1)))
1069 }
1070 Stmt::Return(expr) => {
1071 let result = if let Some(e) = expr {
1074 let mut scope = self.scope.write().await;
1075 let val = eval_expr(e, &mut scope)?;
1076 let code = match val {
1078 Value::Int(n) => n,
1079 Value::Bool(b) => if b { 0 } else { 1 },
1080 _ => 0,
1081 };
1082 ExecResult {
1083 code,
1084 out: String::new(),
1085 err: String::new(),
1086 data: None,
1087 output: None,
1088 }
1089 } else {
1090 ExecResult::success("")
1091 };
1092 Ok(ControlFlow::return_value(result))
1093 }
1094 Stmt::Exit(expr) => {
1095 let code = if let Some(e) = expr {
1096 let mut scope = self.scope.write().await;
1097 let val = eval_expr(e, &mut scope)?;
1098 match val {
1099 Value::Int(n) => n,
1100 _ => 0,
1101 }
1102 } else {
1103 0
1104 };
1105 Ok(ControlFlow::exit_code(code))
1106 }
1107 Stmt::ToolDef(tool_def) => {
1108 let mut user_tools = self.user_tools.write().await;
1109 user_tools.insert(tool_def.name.clone(), tool_def.clone());
1110 Ok(ControlFlow::ok(ExecResult::success("")))
1111 }
1112 Stmt::AndChain { left, right } => {
1113 {
1116 let mut scope = self.scope.write().await;
1117 scope.suppress_errexit();
1118 }
1119 let left_flow = self.execute_stmt_flow(left).await?;
1120 {
1121 let mut scope = self.scope.write().await;
1122 scope.unsuppress_errexit();
1123 }
1124 match left_flow {
1125 ControlFlow::Normal(mut left_result) => {
1126 self.drain_stderr_into(&mut left_result).await;
1127 self.update_last_result(&left_result).await;
1128 if left_result.ok() {
1129 let right_flow = self.execute_stmt_flow(right).await?;
1130 match right_flow {
1131 ControlFlow::Normal(mut right_result) => {
1132 self.drain_stderr_into(&mut right_result).await;
1133 self.update_last_result(&right_result).await;
1134 let mut combined = left_result;
1135 accumulate_result(&mut combined, &right_result);
1136 Ok(ControlFlow::ok(combined))
1137 }
1138 other => Ok(other),
1139 }
1140 } else {
1141 Ok(ControlFlow::ok(left_result))
1142 }
1143 }
1144 _ => Ok(left_flow),
1145 }
1146 }
1147 Stmt::OrChain { left, right } => {
1148 {
1151 let mut scope = self.scope.write().await;
1152 scope.suppress_errexit();
1153 }
1154 let left_flow = self.execute_stmt_flow(left).await?;
1155 {
1156 let mut scope = self.scope.write().await;
1157 scope.unsuppress_errexit();
1158 }
1159 match left_flow {
1160 ControlFlow::Normal(mut left_result) => {
1161 self.drain_stderr_into(&mut left_result).await;
1162 self.update_last_result(&left_result).await;
1163 if !left_result.ok() {
1164 let right_flow = self.execute_stmt_flow(right).await?;
1165 match right_flow {
1166 ControlFlow::Normal(mut right_result) => {
1167 self.drain_stderr_into(&mut right_result).await;
1168 self.update_last_result(&right_result).await;
1169 let mut combined = left_result;
1170 accumulate_result(&mut combined, &right_result);
1171 Ok(ControlFlow::ok(combined))
1172 }
1173 other => Ok(other),
1174 }
1175 } else {
1176 Ok(ControlFlow::ok(left_result))
1177 }
1178 }
1179 _ => Ok(left_flow), }
1181 }
1182 Stmt::Test(test_expr) => {
1183 let expr = crate::ast::Expr::Test(Box::new(test_expr.clone()));
1185 let mut scope = self.scope.write().await;
1186 let value = eval_expr(&expr, &mut scope)?;
1187 drop(scope);
1188 let is_true = match value {
1189 crate::ast::Value::Bool(b) => b,
1190 _ => false,
1191 };
1192 if is_true {
1193 Ok(ControlFlow::ok(ExecResult::success("")))
1194 } else {
1195 Ok(ControlFlow::ok(ExecResult::failure(1, "")))
1196 }
1197 }
1198 Stmt::Empty => Ok(ControlFlow::ok(ExecResult::success(""))),
1199 }
1200 }.instrument(span))
1201 }
1202
1203 #[tracing::instrument(level = "debug", skip(self, pipeline), fields(background = pipeline.background, command_count = pipeline.commands.len()))]
1205 async fn execute_pipeline(&self, pipeline: &crate::ast::Pipeline) -> Result<ExecResult> {
1206 if pipeline.commands.is_empty() {
1207 return Ok(ExecResult::success(""));
1208 }
1209
1210 if pipeline.background {
1212 return self.execute_background(pipeline).await;
1213 }
1214
1215 let mut ctx = {
1223 let ec = self.exec_ctx.read().await;
1224 let scope = self.scope.read().await;
1225 ExecContext {
1226 backend: ec.backend.clone(),
1227 scope: scope.clone(),
1228 cwd: ec.cwd.clone(),
1229 prev_cwd: ec.prev_cwd.clone(),
1230 stdin: None,
1231 stdin_data: None,
1232 pipe_stdin: None,
1233 pipe_stdout: None,
1234 stderr: ec.stderr.clone(),
1235 tool_schemas: ec.tool_schemas.clone(),
1236 tools: ec.tools.clone(),
1237 job_manager: ec.job_manager.clone(),
1238 pipeline_position: PipelinePosition::Only,
1239 interactive: self.interactive,
1240 aliases: ec.aliases.clone(),
1241 ignore_config: ec.ignore_config.clone(),
1242 output_limit: ec.output_limit.clone(),
1243 allow_external_commands: self.allow_external_commands,
1244 nonce_store: ec.nonce_store.clone(),
1245 #[cfg(unix)]
1246 terminal_state: ec.terminal_state.clone(),
1247 }
1248 }; let mut result = self.runner.run(&pipeline.commands, &mut ctx, self).await;
1251
1252 if ctx.output_limit.is_enabled() {
1254 let _ = crate::output_limit::spill_if_needed(&mut result, &ctx.output_limit).await;
1255 }
1256
1257 {
1259 let mut ec = self.exec_ctx.write().await;
1260 ec.cwd = ctx.cwd.clone();
1261 ec.prev_cwd = ctx.prev_cwd.clone();
1262 ec.aliases = ctx.aliases.clone();
1263 ec.ignore_config = ctx.ignore_config.clone();
1264 ec.output_limit = ctx.output_limit.clone();
1265 }
1266 {
1267 let mut scope = self.scope.write().await;
1268 *scope = ctx.scope.clone();
1269 }
1270
1271 Ok(result)
1272 }
1273
1274 #[tracing::instrument(level = "debug", skip(self, pipeline), fields(command_count = pipeline.commands.len()))]
1282 async fn execute_background(&self, pipeline: &crate::ast::Pipeline) -> Result<ExecResult> {
1283 use tokio::sync::oneshot;
1284
1285 let command_str = self.format_pipeline(pipeline);
1287
1288 let stdout = Arc::new(BoundedStream::default_size());
1290 let stderr = Arc::new(BoundedStream::default_size());
1291
1292 let (tx, rx) = oneshot::channel();
1294
1295 let job_id = self.jobs.register_with_streams(
1297 command_str.clone(),
1298 rx,
1299 stdout.clone(),
1300 stderr.clone(),
1301 ).await;
1302
1303 let runner = self.runner.clone();
1305 let commands = pipeline.commands.clone();
1306 let backend = {
1307 let ctx = self.exec_ctx.read().await;
1308 ctx.backend.clone()
1309 };
1310 let scope = {
1311 let scope = self.scope.read().await;
1312 scope.clone()
1313 };
1314 let cwd = {
1315 let ctx = self.exec_ctx.read().await;
1316 ctx.cwd.clone()
1317 };
1318 let tools = self.tools.clone();
1319 let tool_schemas = self.tools.schemas();
1320 let allow_ext = self.allow_external_commands;
1321
1322 tokio::spawn(async move {
1324 let mut bg_ctx = ExecContext::with_backend(backend);
1327 bg_ctx.scope = scope;
1328 bg_ctx.cwd = cwd;
1329 bg_ctx.set_tools(tools.clone());
1330 bg_ctx.set_tool_schemas(tool_schemas);
1331 bg_ctx.allow_external_commands = allow_ext;
1332
1333 let dispatcher = crate::dispatch::BackendDispatcher::new(tools);
1336
1337 let result = runner.run(&commands, &mut bg_ctx, &dispatcher).await;
1339
1340 if !result.out.is_empty() {
1342 stdout.write(result.out.as_bytes()).await;
1343 }
1344 if !result.err.is_empty() {
1345 stderr.write(result.err.as_bytes()).await;
1346 }
1347
1348 stdout.close().await;
1350 stderr.close().await;
1351
1352 let _ = tx.send(result);
1354 });
1355
1356 Ok(ExecResult::success(format!("[{}]", job_id)))
1357 }
1358
1359 fn format_pipeline(&self, pipeline: &crate::ast::Pipeline) -> String {
1361 pipeline.commands
1362 .iter()
1363 .map(|cmd| {
1364 let mut parts = vec![cmd.name.clone()];
1365 for arg in &cmd.args {
1366 match arg {
1367 Arg::Positional(expr) => {
1368 parts.push(self.format_expr(expr));
1369 }
1370 Arg::Named { key, value } => {
1371 parts.push(format!("{}={}", key, self.format_expr(value)));
1372 }
1373 Arg::ShortFlag(name) => {
1374 parts.push(format!("-{}", name));
1375 }
1376 Arg::LongFlag(name) => {
1377 parts.push(format!("--{}", name));
1378 }
1379 Arg::DoubleDash => {
1380 parts.push("--".to_string());
1381 }
1382 }
1383 }
1384 parts.join(" ")
1385 })
1386 .collect::<Vec<_>>()
1387 .join(" | ")
1388 }
1389
1390 fn format_expr(&self, expr: &Expr) -> String {
1392 match expr {
1393 Expr::Literal(Value::String(s)) => {
1394 if s.contains(' ') || s.contains('"') {
1395 format!("'{}'", s.replace('\'', "\\'"))
1396 } else {
1397 s.clone()
1398 }
1399 }
1400 Expr::Literal(Value::Int(i)) => i.to_string(),
1401 Expr::Literal(Value::Float(f)) => f.to_string(),
1402 Expr::Literal(Value::Bool(b)) => b.to_string(),
1403 Expr::Literal(Value::Null) => "null".to_string(),
1404 Expr::VarRef(path) => {
1405 let name = path.segments.iter()
1406 .map(|seg| match seg {
1407 crate::ast::VarSegment::Field(f) => f.clone(),
1408 })
1409 .collect::<Vec<_>>()
1410 .join(".");
1411 format!("${{{}}}", name)
1412 }
1413 Expr::Interpolated(_) => "\"...\"".to_string(),
1414 _ => "...".to_string(),
1415 }
1416 }
1417
1418 async fn execute_command(&self, name: &str, args: &[Arg]) -> Result<ExecResult> {
1420 self.execute_command_depth(name, args, 0).await
1421 }
1422
1423 #[tracing::instrument(level = "info", skip(self, args, alias_depth), fields(command = %name), err)]
1424 async fn execute_command_depth(&self, name: &str, args: &[Arg], alias_depth: u8) -> Result<ExecResult> {
1425 match name {
1427 "true" => return Ok(ExecResult::success("")),
1428 "false" => return Ok(ExecResult::failure(1, "")),
1429 "source" | "." => return self.execute_source(args).await,
1430 _ => {}
1431 }
1432
1433 if alias_depth < 10 {
1435 let alias_value = {
1436 let ctx = self.exec_ctx.read().await;
1437 ctx.aliases.get(name).cloned()
1438 };
1439 if let Some(alias_val) = alias_value {
1440 let parts: Vec<&str> = alias_val.split_whitespace().collect();
1442 if let Some((alias_cmd, alias_args)) = parts.split_first() {
1443 let mut new_args: Vec<Arg> = alias_args
1444 .iter()
1445 .map(|a| Arg::Positional(Expr::Literal(Value::String(a.to_string()))))
1446 .collect();
1447 new_args.extend_from_slice(args);
1448 return Box::pin(self.execute_command_depth(alias_cmd, &new_args, alias_depth + 1)).await;
1449 }
1450 }
1451 }
1452
1453 if let Some(builtin_name) = name.strip_prefix("/v/bin/") {
1455 return match self.tools.get(builtin_name) {
1456 Some(_) => Box::pin(self.execute_command_depth(builtin_name, args, alias_depth)).await,
1457 None => Ok(ExecResult::failure(127, format!("command not found: {}", name))),
1458 };
1459 }
1460
1461 {
1463 let user_tools = self.user_tools.read().await;
1464 if let Some(tool_def) = user_tools.get(name) {
1465 let tool_def = tool_def.clone();
1466 drop(user_tools);
1467 return self.execute_user_tool(tool_def, args).await;
1468 }
1469 }
1470
1471 let tool = match self.tools.get(name) {
1473 Some(t) => t,
1474 None => {
1475 if let Some(result) = self.try_execute_script(name, args).await? {
1477 return Ok(result);
1478 }
1479 if let Some(result) = self.try_execute_external(name, args).await? {
1481 return Ok(result);
1482 }
1483
1484 let backend = self.exec_ctx.read().await.backend.clone();
1489 let tool_schema = backend.get_tool(name).await.ok().flatten().map(|t| {
1490 let mut s = t.schema;
1491 s.map_positionals = true;
1492 s
1493 });
1494 let tool_args = self.build_args_async(args, tool_schema.as_ref()).await?;
1495 let mut ctx = self.exec_ctx.write().await;
1496 {
1497 let scope = self.scope.read().await;
1498 ctx.scope = scope.clone();
1499 }
1500 let backend = ctx.backend.clone();
1501 match backend.call_tool(name, tool_args, &mut ctx).await {
1502 Ok(tool_result) => {
1503 let mut scope = self.scope.write().await;
1504 *scope = ctx.scope.clone();
1505 let mut exec = ExecResult::from_output(
1506 tool_result.code as i64, tool_result.stdout, tool_result.stderr,
1507 );
1508 exec.output = tool_result.output;
1509 return Ok(exec);
1510 }
1511 Err(BackendError::ToolNotFound(_)) => {
1512 }
1514 Err(e) => {
1515 tracing::debug!("backend error for {name}: {e}");
1518 }
1519 }
1520
1521 return Ok(ExecResult::failure(127, format!("command not found: {}", name)));
1522 }
1523 };
1524
1525 let schema = tool.schema();
1527 let mut tool_args = self.build_args_async(args, Some(&schema)).await?;
1528 let output_format = extract_output_format(&mut tool_args, Some(&schema));
1529
1530 let schema_claims = |flag: &str| -> bool {
1532 let bare = flag.trim_start_matches('-');
1533 schema.params.iter().any(|p| p.matches_flag(flag) || p.matches_flag(bare))
1534 };
1535 let wants_help =
1536 (tool_args.flags.contains("help") && !schema_claims("help"))
1537 || (tool_args.flags.contains("h") && !schema_claims("-h"));
1538 if wants_help {
1539 let help_topic = crate::help::HelpTopic::Tool(name.to_string());
1540 let ctx = self.exec_ctx.read().await;
1541 let content = crate::help::get_help(&help_topic, &ctx.tool_schemas);
1542 return Ok(ExecResult::with_output(crate::interpreter::OutputData::text(content)));
1543 }
1544
1545 let mut ctx = self.exec_ctx.write().await;
1547 {
1548 let scope = self.scope.read().await;
1549 ctx.scope = scope.clone();
1550 }
1551
1552 let result = tool.execute(tool_args, &mut ctx).await;
1553
1554 {
1556 let mut scope = self.scope.write().await;
1557 *scope = ctx.scope.clone();
1558 }
1559
1560 let result = match output_format {
1561 Some(format) => apply_output_format(result, format),
1562 None => result,
1563 };
1564
1565 Ok(result)
1566 }
1567
1568 async fn build_args_async(&self, args: &[Arg], schema: Option<&crate::tools::ToolSchema>) -> Result<ToolArgs> {
1572 let mut tool_args = ToolArgs::new();
1573 let param_lookup = schema.map(schema_param_lookup).unwrap_or_default();
1574
1575 let mut consumed: std::collections::HashSet<usize> = std::collections::HashSet::new();
1577 let mut past_double_dash = false;
1578
1579 let positional_indices: Vec<usize> = args.iter().enumerate()
1581 .filter_map(|(i, a)| matches!(a, Arg::Positional(_)).then_some(i))
1582 .collect();
1583
1584 let mut i = 0;
1585 while i < args.len() {
1586 match &args[i] {
1587 Arg::DoubleDash => {
1588 past_double_dash = true;
1589 }
1590 Arg::Positional(expr) => {
1591 if !consumed.contains(&i) {
1592 let value = self.eval_expr_async(expr).await?;
1593 let value = apply_tilde_expansion(value);
1594 tool_args.positional.push(value);
1595 }
1596 }
1597 Arg::Named { key, value } => {
1598 let val = self.eval_expr_async(value).await?;
1599 let val = apply_tilde_expansion(val);
1600 tool_args.named.insert(key.clone(), val);
1601 }
1602 Arg::ShortFlag(name) => {
1603 if past_double_dash {
1604 tool_args.positional.push(Value::String(format!("-{name}")));
1605 } else if name.len() == 1 {
1606 let flag_name = name.as_str();
1607 let lookup = param_lookup.get(flag_name);
1608 let is_bool = lookup.map(|(_, typ)| is_bool_type(typ)).unwrap_or(true);
1609
1610 if is_bool {
1611 tool_args.flags.insert(flag_name.to_string());
1612 } else {
1613 let canonical = lookup.map(|(name, _)| *name).unwrap_or(flag_name);
1615 let next_pos = positional_indices.iter()
1616 .find(|idx| **idx > i && !consumed.contains(idx));
1617
1618 if let Some(&pos_idx) = next_pos {
1619 if let Arg::Positional(expr) = &args[pos_idx] {
1620 let value = self.eval_expr_async(expr).await?;
1621 let value = apply_tilde_expansion(value);
1622 tool_args.named.insert(canonical.to_string(), value);
1623 consumed.insert(pos_idx);
1624 }
1625 } else {
1626 tool_args.flags.insert(flag_name.to_string());
1627 }
1628 }
1629 } else {
1630 for c in name.chars() {
1632 tool_args.flags.insert(c.to_string());
1633 }
1634 }
1635 }
1636 Arg::LongFlag(name) => {
1637 if past_double_dash {
1638 tool_args.positional.push(Value::String(format!("--{name}")));
1639 } else {
1640 let lookup = param_lookup.get(name.as_str());
1641 let is_bool = lookup.map(|(_, typ)| is_bool_type(typ)).unwrap_or(true);
1642
1643 if is_bool {
1644 tool_args.flags.insert(name.clone());
1645 } else {
1646 let canonical = lookup.map(|(name, _)| *name).unwrap_or(name.as_str());
1647 let next_pos = positional_indices.iter()
1648 .find(|idx| **idx > i && !consumed.contains(idx));
1649
1650 if let Some(&pos_idx) = next_pos {
1651 if let Arg::Positional(expr) = &args[pos_idx] {
1652 let value = self.eval_expr_async(expr).await?;
1653 let value = apply_tilde_expansion(value);
1654 tool_args.named.insert(canonical.to_string(), value);
1655 consumed.insert(pos_idx);
1656 }
1657 } else {
1658 tool_args.flags.insert(name.clone());
1659 }
1660 }
1661 }
1662 }
1663 }
1664 i += 1;
1665 }
1666
1667 if let Some(schema) = schema.filter(|s| s.map_positionals) {
1672 let pre_dash_count = if past_double_dash {
1673 let dash_pos = args.iter().position(|a| matches!(a, Arg::DoubleDash)).unwrap_or(args.len());
1674 positional_indices.iter()
1675 .filter(|idx| **idx < dash_pos && !consumed.contains(idx))
1676 .count()
1677 } else {
1678 tool_args.positional.len()
1679 };
1680
1681 let mut remaining = Vec::new();
1682 let mut positional_iter = tool_args.positional.drain(..).enumerate();
1683
1684 for param in &schema.params {
1685 if tool_args.named.contains_key(¶m.name) || tool_args.flags.contains(¶m.name) {
1686 continue;
1687 }
1688 if is_bool_type(¶m.param_type) {
1689 continue;
1690 }
1691 loop {
1692 match positional_iter.next() {
1693 Some((idx, val)) if idx < pre_dash_count => {
1694 tool_args.named.insert(param.name.clone(), val);
1695 break;
1696 }
1697 Some((_, val)) => {
1698 remaining.push(val);
1699 }
1700 None => break,
1701 }
1702 }
1703 }
1704
1705 remaining.extend(positional_iter.map(|(_, v)| v));
1706 tool_args.positional = remaining;
1707 }
1708
1709 Ok(tool_args)
1710 }
1711
1712 async fn build_args_flat(&self, args: &[Arg]) -> Result<Vec<String>> {
1722 let mut argv = Vec::new();
1723 for arg in args {
1724 match arg {
1725 Arg::Positional(expr) => {
1726 let value = self.eval_expr_async(expr).await?;
1727 let value = apply_tilde_expansion(value);
1728 argv.push(value_to_string(&value));
1729 }
1730 Arg::Named { key, value } => {
1731 let val = self.eval_expr_async(value).await?;
1732 let val = apply_tilde_expansion(val);
1733 argv.push(format!("{}={}", key, value_to_string(&val)));
1734 }
1735 Arg::ShortFlag(name) => {
1736 argv.push(format!("-{}", name));
1738 }
1739 Arg::LongFlag(name) => {
1740 argv.push(format!("--{}", name));
1742 }
1743 Arg::DoubleDash => {
1744 argv.push("--".to_string());
1746 }
1747 }
1748 }
1749 Ok(argv)
1750 }
1751
1752 fn eval_expr_async<'a>(&'a self, expr: &'a Expr) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Value>> + Send + 'a>> {
1757 Box::pin(async move {
1758 match expr {
1759 Expr::Literal(value) => Ok(value.clone()),
1760 Expr::VarRef(path) => {
1761 let scope = self.scope.read().await;
1762 scope.resolve_path(path)
1763 .ok_or_else(|| anyhow::anyhow!("undefined variable"))
1764 }
1765 Expr::Interpolated(parts) => {
1766 let mut result = String::new();
1767 for part in parts {
1768 result.push_str(&self.eval_string_part_async(part).await?);
1769 }
1770 Ok(Value::String(result))
1771 }
1772 Expr::BinaryOp { left, op, right } => {
1773 match op {
1774 BinaryOp::And => {
1775 let left_val = self.eval_expr_async(left).await?;
1776 if !is_truthy(&left_val) {
1777 return Ok(left_val);
1778 }
1779 self.eval_expr_async(right).await
1780 }
1781 BinaryOp::Or => {
1782 let left_val = self.eval_expr_async(left).await?;
1783 if is_truthy(&left_val) {
1784 return Ok(left_val);
1785 }
1786 self.eval_expr_async(right).await
1787 }
1788 _ => {
1789 let mut scope = self.scope.write().await;
1791 eval_expr(expr, &mut scope).map_err(|e| anyhow::anyhow!("{}", e))
1792 }
1793 }
1794 }
1795 Expr::CommandSubst(pipeline) => {
1796 let saved_scope = { self.scope.read().await.clone() };
1799 let saved_cwd = {
1800 let ec = self.exec_ctx.read().await;
1801 (ec.cwd.clone(), ec.prev_cwd.clone())
1802 };
1803
1804 let run_result = self.execute_pipeline(pipeline).await;
1806
1807 {
1809 let mut scope = self.scope.write().await;
1810 *scope = saved_scope;
1811 if let Ok(ref r) = run_result {
1812 scope.set_last_result(r.clone());
1813 }
1814 }
1815 {
1816 let mut ec = self.exec_ctx.write().await;
1817 ec.cwd = saved_cwd.0;
1818 ec.prev_cwd = saved_cwd.1;
1819 }
1820
1821 let result = run_result?;
1823
1824 if let Some(data) = &result.data {
1826 Ok(data.clone())
1827 } else if let Some(ref output) = result.output {
1828 if output.is_flat() && !output.is_simple_text() && !output.root.is_empty() {
1830 let items: Vec<serde_json::Value> = output.root.iter()
1831 .map(|n| serde_json::Value::String(n.display_name().to_string()))
1832 .collect();
1833 Ok(Value::Json(serde_json::Value::Array(items)))
1834 } else {
1835 Ok(Value::String(result.out.trim_end().to_string()))
1836 }
1837 } else {
1838 Ok(Value::String(result.out.trim_end().to_string()))
1840 }
1841 }
1842 Expr::Test(test_expr) => {
1843 let expr = Expr::Test(test_expr.clone());
1845 let mut scope = self.scope.write().await;
1846 eval_expr(&expr, &mut scope).map_err(|e| anyhow::anyhow!("{}", e))
1847 }
1848 Expr::Positional(n) => {
1849 let scope = self.scope.read().await;
1850 match scope.get_positional(*n) {
1851 Some(s) => Ok(Value::String(s.to_string())),
1852 None => Ok(Value::String(String::new())),
1853 }
1854 }
1855 Expr::AllArgs => {
1856 let scope = self.scope.read().await;
1857 Ok(Value::String(scope.all_args().join(" ")))
1858 }
1859 Expr::ArgCount => {
1860 let scope = self.scope.read().await;
1861 Ok(Value::Int(scope.arg_count() as i64))
1862 }
1863 Expr::VarLength(name) => {
1864 let scope = self.scope.read().await;
1865 match scope.get(name) {
1866 Some(value) => Ok(Value::Int(value_to_string(value).len() as i64)),
1867 None => Ok(Value::Int(0)),
1868 }
1869 }
1870 Expr::VarWithDefault { name, default } => {
1871 let scope = self.scope.read().await;
1872 let use_default = match scope.get(name) {
1873 Some(value) => value_to_string(value).is_empty(),
1874 None => true,
1875 };
1876 drop(scope); if use_default {
1878 self.eval_string_parts_async(default).await.map(Value::String)
1880 } else {
1881 let scope = self.scope.read().await;
1882 scope.get(name).cloned().ok_or_else(|| anyhow::anyhow!("variable '{}' not found", name))
1883 }
1884 }
1885 Expr::Arithmetic(expr_str) => {
1886 let scope = self.scope.read().await;
1887 crate::arithmetic::eval_arithmetic(expr_str, &scope)
1888 .map(Value::Int)
1889 .map_err(|e| anyhow::anyhow!("arithmetic error: {}", e))
1890 }
1891 Expr::Command(cmd) => {
1892 let result = self.execute_command(&cmd.name, &cmd.args).await?;
1894 Ok(Value::Bool(result.code == 0))
1895 }
1896 Expr::LastExitCode => {
1897 let scope = self.scope.read().await;
1898 Ok(Value::Int(scope.last_result().code))
1899 }
1900 Expr::CurrentPid => {
1901 let scope = self.scope.read().await;
1902 Ok(Value::Int(scope.pid() as i64))
1903 }
1904 }
1905 })
1906 }
1907
1908 fn eval_string_parts_async<'a>(&'a self, parts: &'a [StringPart]) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<String>> + Send + 'a>> {
1910 Box::pin(async move {
1911 let mut result = String::new();
1912 for part in parts {
1913 result.push_str(&self.eval_string_part_async(part).await?);
1914 }
1915 Ok(result)
1916 })
1917 }
1918
1919 fn eval_string_part_async<'a>(&'a self, part: &'a StringPart) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<String>> + Send + 'a>> {
1921 Box::pin(async move {
1922 match part {
1923 StringPart::Literal(s) => Ok(s.clone()),
1924 StringPart::Var(path) => {
1925 let scope = self.scope.read().await;
1926 match scope.resolve_path(path) {
1927 Some(value) => Ok(value_to_string(&value)),
1928 None => Ok(String::new()), }
1930 }
1931 StringPart::VarWithDefault { name, default } => {
1932 let scope = self.scope.read().await;
1933 let use_default = match scope.get(name) {
1934 Some(value) => value_to_string(value).is_empty(),
1935 None => true,
1936 };
1937 drop(scope); if use_default {
1939 self.eval_string_parts_async(default).await
1941 } else {
1942 let scope = self.scope.read().await;
1943 Ok(value_to_string(scope.get(name).ok_or_else(|| anyhow::anyhow!("variable '{}' not found", name))?))
1944 }
1945 }
1946 StringPart::VarLength(name) => {
1947 let scope = self.scope.read().await;
1948 match scope.get(name) {
1949 Some(value) => Ok(value_to_string(value).len().to_string()),
1950 None => Ok("0".to_string()),
1951 }
1952 }
1953 StringPart::Positional(n) => {
1954 let scope = self.scope.read().await;
1955 match scope.get_positional(*n) {
1956 Some(s) => Ok(s.to_string()),
1957 None => Ok(String::new()),
1958 }
1959 }
1960 StringPart::AllArgs => {
1961 let scope = self.scope.read().await;
1962 Ok(scope.all_args().join(" "))
1963 }
1964 StringPart::ArgCount => {
1965 let scope = self.scope.read().await;
1966 Ok(scope.arg_count().to_string())
1967 }
1968 StringPart::Arithmetic(expr) => {
1969 let scope = self.scope.read().await;
1970 match crate::arithmetic::eval_arithmetic(expr, &scope) {
1971 Ok(value) => Ok(value.to_string()),
1972 Err(_) => Ok(String::new()),
1973 }
1974 }
1975 StringPart::CommandSubst(pipeline) => {
1976 let saved_scope = { self.scope.read().await.clone() };
1979 let saved_cwd = {
1980 let ec = self.exec_ctx.read().await;
1981 (ec.cwd.clone(), ec.prev_cwd.clone())
1982 };
1983
1984 let run_result = self.execute_pipeline(pipeline).await;
1986
1987 {
1989 let mut scope = self.scope.write().await;
1990 *scope = saved_scope;
1991 if let Ok(ref r) = run_result {
1992 scope.set_last_result(r.clone());
1993 }
1994 }
1995 {
1996 let mut ec = self.exec_ctx.write().await;
1997 ec.cwd = saved_cwd.0;
1998 ec.prev_cwd = saved_cwd.1;
1999 }
2000
2001 let result = run_result?;
2003
2004 Ok(result.out.trim_end_matches('\n').to_string())
2005 }
2006 StringPart::LastExitCode => {
2007 let scope = self.scope.read().await;
2008 Ok(scope.last_result().code.to_string())
2009 }
2010 StringPart::CurrentPid => {
2011 let scope = self.scope.read().await;
2012 Ok(scope.pid().to_string())
2013 }
2014 }
2015 })
2016 }
2017
2018 async fn update_last_result(&self, result: &ExecResult) {
2020 let mut scope = self.scope.write().await;
2021 scope.set_last_result(result.clone());
2022 }
2023
2024 async fn drain_stderr_into(&self, result: &mut ExecResult) {
2030 let drained = {
2031 let mut receiver = self.stderr_receiver.lock().await;
2032 receiver.drain_lossy()
2033 };
2034 if !drained.is_empty() {
2035 if !result.err.is_empty() && !result.err.ends_with('\n') {
2036 result.err.push('\n');
2037 }
2038 result.err.push_str(&drained);
2039 }
2040 }
2041
2042 async fn execute_user_tool(&self, def: ToolDef, args: &[Arg]) -> Result<ExecResult> {
2048 let tool_args = self.build_args_async(args, None).await?;
2050
2051 {
2053 let mut scope = self.scope.write().await;
2054 scope.push_frame();
2055 }
2056
2057 let saved_positional = {
2059 let mut scope = self.scope.write().await;
2060 let saved = scope.save_positional();
2061
2062 let positional_args: Vec<String> = tool_args.positional
2064 .iter()
2065 .map(value_to_string)
2066 .collect();
2067 scope.set_positional(&def.name, positional_args);
2068
2069 saved
2070 };
2071
2072 let mut accumulated_out = String::new();
2075 let mut accumulated_err = String::new();
2076 let mut last_code = 0i64;
2077 let mut last_data: Option<Value> = None;
2078
2079 let mut exec_error: Option<anyhow::Error> = None;
2081 let mut exit_code: Option<i64> = None;
2082
2083 for stmt in &def.body {
2084 match self.execute_stmt_flow(stmt).await {
2085 Ok(flow) => {
2086 let drained = {
2088 let mut receiver = self.stderr_receiver.lock().await;
2089 receiver.drain_lossy()
2090 };
2091 if !drained.is_empty() {
2092 accumulated_err.push_str(&drained);
2093 }
2094
2095 match flow {
2096 ControlFlow::Normal(r) => {
2097 accumulated_out.push_str(&r.out);
2098 accumulated_err.push_str(&r.err);
2099 last_code = r.code;
2100 last_data = r.data;
2101 }
2102 ControlFlow::Return { value } => {
2103 accumulated_out.push_str(&value.out);
2104 accumulated_err.push_str(&value.err);
2105 last_code = value.code;
2106 last_data = value.data;
2107 break;
2108 }
2109 ControlFlow::Exit { code } => {
2110 exit_code = Some(code);
2111 break;
2112 }
2113 ControlFlow::Break { result: r, .. } | ControlFlow::Continue { result: r, .. } => {
2114 accumulated_out.push_str(&r.out);
2115 accumulated_err.push_str(&r.err);
2116 last_code = r.code;
2117 last_data = r.data;
2118 }
2119 }
2120 }
2121 Err(e) => {
2122 exec_error = Some(e);
2123 break;
2124 }
2125 }
2126 }
2127
2128 {
2130 let mut scope = self.scope.write().await;
2131 scope.pop_frame();
2132 scope.set_positional(saved_positional.0, saved_positional.1);
2133 }
2134
2135 if let Some(e) = exec_error {
2137 return Err(e);
2138 }
2139 if let Some(code) = exit_code {
2140 return Ok(ExecResult {
2141 code,
2142 out: accumulated_out,
2143 err: accumulated_err,
2144 data: last_data,
2145 output: None,
2146 });
2147 }
2148
2149 Ok(ExecResult {
2150 code: last_code,
2151 out: accumulated_out,
2152 err: accumulated_err,
2153 data: last_data,
2154 output: None,
2155 })
2156 }
2157
2158 async fn execute_source(&self, args: &[Arg]) -> Result<ExecResult> {
2163 let tool_args = self.build_args_async(args, None).await?;
2165 let path = match tool_args.positional.first() {
2166 Some(Value::String(s)) => s.clone(),
2167 Some(v) => value_to_string(v),
2168 None => {
2169 return Ok(ExecResult::failure(1, "source: missing filename"));
2170 }
2171 };
2172
2173 let full_path = {
2175 let ctx = self.exec_ctx.read().await;
2176 if path.starts_with('/') {
2177 std::path::PathBuf::from(&path)
2178 } else {
2179 ctx.cwd.join(&path)
2180 }
2181 };
2182
2183 let content = {
2185 let ctx = self.exec_ctx.read().await;
2186 match ctx.backend.read(&full_path, None).await {
2187 Ok(bytes) => {
2188 String::from_utf8(bytes).map_err(|e| {
2189 anyhow::anyhow!("source: {}: invalid UTF-8: {}", path, e)
2190 })?
2191 }
2192 Err(e) => {
2193 return Ok(ExecResult::failure(
2194 1,
2195 format!("source: {}: {}", path, e),
2196 ));
2197 }
2198 }
2199 };
2200
2201 let program = match crate::parser::parse(&content) {
2203 Ok(p) => p,
2204 Err(errors) => {
2205 let msg = errors
2206 .iter()
2207 .map(|e| format!("{}:{}: {}", path, e.span.start, e.message))
2208 .collect::<Vec<_>>()
2209 .join("\n");
2210 return Ok(ExecResult::failure(1, format!("source: {}", msg)));
2211 }
2212 };
2213
2214 let mut result = ExecResult::success("");
2216 for stmt in program.statements {
2217 if matches!(stmt, crate::ast::Stmt::Empty) {
2218 continue;
2219 }
2220
2221 match self.execute_stmt_flow(&stmt).await {
2222 Ok(flow) => {
2223 self.drain_stderr_into(&mut result).await;
2224 match flow {
2225 ControlFlow::Normal(r) => {
2226 result = r.clone();
2227 self.update_last_result(&r).await;
2228 }
2229 ControlFlow::Break { .. } | ControlFlow::Continue { .. } => {
2230 return Err(anyhow::anyhow!(
2231 "source: {}: unexpected break/continue outside loop",
2232 path
2233 ));
2234 }
2235 ControlFlow::Return { value } => {
2236 return Ok(value);
2237 }
2238 ControlFlow::Exit { code } => {
2239 result.code = code;
2240 return Ok(result);
2241 }
2242 }
2243 }
2244 Err(e) => {
2245 return Err(e.context(format!("source: {}", path)));
2246 }
2247 }
2248 }
2249
2250 Ok(result)
2251 }
2252
2253 async fn try_execute_script(&self, name: &str, args: &[Arg]) -> Result<Option<ExecResult>> {
2258 let path_value = {
2260 let scope = self.scope.read().await;
2261 scope
2262 .get("PATH")
2263 .map(value_to_string)
2264 .unwrap_or_else(|| "/bin".to_string())
2265 };
2266
2267 for dir in path_value.split(':') {
2269 if dir.is_empty() {
2270 continue;
2271 }
2272
2273 let script_path = PathBuf::from(dir).join(format!("{}.kai", name));
2275
2276 let exists = {
2278 let ctx = self.exec_ctx.read().await;
2279 ctx.backend.exists(&script_path).await
2280 };
2281
2282 if !exists {
2283 continue;
2284 }
2285
2286 let content = {
2288 let ctx = self.exec_ctx.read().await;
2289 match ctx.backend.read(&script_path, None).await {
2290 Ok(bytes) => match String::from_utf8(bytes) {
2291 Ok(s) => s,
2292 Err(e) => {
2293 return Ok(Some(ExecResult::failure(
2294 1,
2295 format!("{}: invalid UTF-8: {}", script_path.display(), e),
2296 )));
2297 }
2298 },
2299 Err(e) => {
2300 return Ok(Some(ExecResult::failure(
2301 1,
2302 format!("{}: {}", script_path.display(), e),
2303 )));
2304 }
2305 }
2306 };
2307
2308 let program = match crate::parser::parse(&content) {
2310 Ok(p) => p,
2311 Err(errors) => {
2312 let msg = errors
2313 .iter()
2314 .map(|e| format!("{}:{}: {}", script_path.display(), e.span.start, e.message))
2315 .collect::<Vec<_>>()
2316 .join("\n");
2317 return Ok(Some(ExecResult::failure(1, msg)));
2318 }
2319 };
2320
2321 let tool_args = self.build_args_async(args, None).await?;
2323
2324 let mut isolated_scope = Scope::new();
2326
2327 let positional_args: Vec<String> = tool_args.positional
2329 .iter()
2330 .map(value_to_string)
2331 .collect();
2332 isolated_scope.set_positional(name, positional_args);
2333
2334 let original_scope = {
2336 let mut scope = self.scope.write().await;
2337 std::mem::replace(&mut *scope, isolated_scope)
2338 };
2339
2340 let mut result = ExecResult::success("");
2342 let mut exec_error: Option<anyhow::Error> = None;
2343 let mut exit_code: Option<i64> = None;
2344
2345 for stmt in program.statements {
2346 if matches!(stmt, crate::ast::Stmt::Empty) {
2347 continue;
2348 }
2349
2350 match self.execute_stmt_flow(&stmt).await {
2351 Ok(flow) => {
2352 match flow {
2353 ControlFlow::Normal(r) => result = r,
2354 ControlFlow::Return { value } => {
2355 result = value;
2356 break;
2357 }
2358 ControlFlow::Exit { code } => {
2359 exit_code = Some(code);
2360 break;
2361 }
2362 ControlFlow::Break { result: r, .. } | ControlFlow::Continue { result: r, .. } => {
2363 result = r;
2364 }
2365 }
2366 }
2367 Err(e) => {
2368 exec_error = Some(e);
2369 break;
2370 }
2371 }
2372 }
2373
2374 {
2376 let mut scope = self.scope.write().await;
2377 *scope = original_scope;
2378 }
2379
2380 if let Some(e) = exec_error {
2382 return Err(e.context(format!("script: {}", script_path.display())));
2383 }
2384 if let Some(code) = exit_code {
2385 result.code = code;
2386 return Ok(Some(result));
2387 }
2388
2389 return Ok(Some(result));
2390 }
2391
2392 Ok(None)
2394 }
2395
2396 #[tracing::instrument(level = "debug", skip(self, args), fields(command = %name))]
2410 async fn try_execute_external(&self, name: &str, args: &[Arg]) -> Result<Option<ExecResult>> {
2411 if !self.allow_external_commands {
2412 return Ok(None);
2413 }
2414
2415 let real_cwd = {
2420 let ctx = self.exec_ctx.read().await;
2421 match ctx.backend.resolve_real_path(&ctx.cwd) {
2422 Some(p) => p,
2423 None => return Ok(None),
2424 }
2425 };
2426
2427 let executable = if name.contains('/') {
2428 let resolved = if std::path::Path::new(name).is_absolute() {
2430 std::path::PathBuf::from(name)
2431 } else {
2432 real_cwd.join(name)
2433 };
2434 if !resolved.exists() {
2435 return Ok(Some(ExecResult::failure(
2436 127,
2437 format!("{}: No such file or directory", name),
2438 )));
2439 }
2440 if !resolved.is_file() {
2441 return Ok(Some(ExecResult::failure(
2442 126,
2443 format!("{}: Is a directory", name),
2444 )));
2445 }
2446 #[cfg(unix)]
2447 {
2448 use std::os::unix::fs::PermissionsExt;
2449 let mode = std::fs::metadata(&resolved)
2450 .map(|m| m.permissions().mode())
2451 .unwrap_or(0);
2452 if mode & 0o111 == 0 {
2453 return Ok(Some(ExecResult::failure(
2454 126,
2455 format!("{}: Permission denied", name),
2456 )));
2457 }
2458 }
2459 resolved.to_string_lossy().into_owned()
2460 } else {
2461 let path_var = {
2463 let scope = self.scope.read().await;
2464 scope
2465 .get("PATH")
2466 .map(value_to_string)
2467 .unwrap_or_else(|| std::env::var("PATH").unwrap_or_default())
2468 };
2469
2470 match resolve_in_path(name, &path_var) {
2472 Some(path) => path,
2473 None => return Ok(None), }
2475 };
2476
2477 tracing::debug!(executable = %executable, "resolved external command");
2478
2479 let argv = self.build_args_flat(args).await?;
2481
2482 let stdin_data = {
2484 let mut ctx = self.exec_ctx.write().await;
2485 ctx.take_stdin()
2486 };
2487
2488 use tokio::process::Command;
2490
2491 let mut cmd = Command::new(&executable);
2492 cmd.args(&argv);
2493 cmd.current_dir(&real_cwd);
2494
2495 cmd.stdin(if stdin_data.is_some() {
2497 std::process::Stdio::piped()
2498 } else if self.interactive {
2499 std::process::Stdio::inherit()
2500 } else {
2501 std::process::Stdio::null()
2502 });
2503
2504 let pipeline_position = {
2508 let ctx = self.exec_ctx.read().await;
2509 ctx.pipeline_position
2510 };
2511 let inherit_output = self.interactive
2512 && matches!(pipeline_position, PipelinePosition::Only | PipelinePosition::Last);
2513
2514 if inherit_output {
2515 cmd.stdout(std::process::Stdio::inherit());
2516 cmd.stderr(std::process::Stdio::inherit());
2517 } else {
2518 cmd.stdout(std::process::Stdio::piped());
2519 cmd.stderr(std::process::Stdio::piped());
2520 }
2521
2522 #[cfg(unix)]
2526 if self.terminal_state.is_some() && inherit_output {
2527 #[allow(unsafe_code)]
2529 unsafe {
2530 cmd.pre_exec(|| {
2531 nix::unistd::setpgid(nix::unistd::Pid::from_raw(0), nix::unistd::Pid::from_raw(0))
2533 .map_err(|e| std::io::Error::from_raw_os_error(e as i32))?;
2534 use nix::libc::{sigaction, SIGTSTP, SIGTTOU, SIGTTIN, SIGINT, SIG_DFL};
2536 let mut sa: nix::libc::sigaction = std::mem::zeroed();
2537 sa.sa_sigaction = SIG_DFL;
2538 sigaction(SIGTSTP, &sa, std::ptr::null_mut());
2539 sigaction(SIGTTOU, &sa, std::ptr::null_mut());
2540 sigaction(SIGTTIN, &sa, std::ptr::null_mut());
2541 sigaction(SIGINT, &sa, std::ptr::null_mut());
2542 Ok(())
2543 });
2544 }
2545 }
2546
2547 let mut child = match cmd.spawn() {
2549 Ok(child) => child,
2550 Err(e) => {
2551 return Ok(Some(ExecResult::failure(
2552 127,
2553 format!("{}: {}", name, e),
2554 )));
2555 }
2556 };
2557
2558 if let Some(data) = stdin_data
2560 && let Some(mut stdin) = child.stdin.take()
2561 {
2562 use tokio::io::AsyncWriteExt;
2563 if let Err(e) = stdin.write_all(data.as_bytes()).await {
2564 return Ok(Some(ExecResult::failure(
2565 1,
2566 format!("{}: failed to write stdin: {}", name, e),
2567 )));
2568 }
2569 }
2571
2572 if inherit_output {
2573 #[cfg(unix)]
2575 if let Some(ref term) = self.terminal_state {
2576 let child_id = child.id().unwrap_or(0);
2577 let pid = nix::unistd::Pid::from_raw(child_id as i32);
2578 let pgid = pid; if let Err(e) = term.give_terminal_to(pgid) {
2582 tracing::warn!("failed to give terminal to child: {}", e);
2583 }
2584
2585 let term_clone = term.clone();
2586 let cmd_name = name.to_string();
2587 let cmd_display = format!("{} {}", name, argv.join(" "));
2588 let jobs = self.jobs.clone();
2589
2590 let code = tokio::task::block_in_place(move || {
2591 let result = term_clone.wait_for_foreground(pid);
2592
2593 if let Err(e) = term_clone.reclaim_terminal() {
2595 tracing::warn!("failed to reclaim terminal: {}", e);
2596 }
2597
2598 match result {
2599 crate::terminal::WaitResult::Exited(code) => code as i64,
2600 crate::terminal::WaitResult::Signaled(sig) => 128 + sig as i64,
2601 crate::terminal::WaitResult::Stopped(_sig) => {
2602 let rt = tokio::runtime::Handle::current();
2604 let job_id = rt.block_on(jobs.register_stopped(
2605 cmd_display,
2606 child_id,
2607 child_id, ));
2609 eprintln!("\n[{}]+ Stopped\t{}", job_id, cmd_name);
2610 148 }
2612 }
2613 });
2614
2615 return Ok(Some(ExecResult::from_output(code, String::new(), String::new())));
2616 }
2617
2618 let status = match child.wait().await {
2620 Ok(s) => s,
2621 Err(e) => {
2622 return Ok(Some(ExecResult::failure(
2623 1,
2624 format!("{}: failed to wait: {}", name, e),
2625 )));
2626 }
2627 };
2628
2629 let code = status.code().unwrap_or_else(|| {
2630 #[cfg(unix)]
2631 {
2632 use std::os::unix::process::ExitStatusExt;
2633 128 + status.signal().unwrap_or(0)
2634 }
2635 #[cfg(not(unix))]
2636 {
2637 -1
2638 }
2639 }) as i64;
2640
2641 Ok(Some(ExecResult::from_output(code, String::new(), String::new())))
2643 } else {
2644 let stdout_stream = Arc::new(BoundedStream::new(DEFAULT_STREAM_MAX_SIZE));
2646 let stderr_stream = Arc::new(BoundedStream::new(DEFAULT_STREAM_MAX_SIZE));
2647
2648 let stdout_pipe = child.stdout.take();
2649 let stderr_pipe = child.stderr.take();
2650
2651 let stdout_clone = stdout_stream.clone();
2652 let stderr_clone = stderr_stream.clone();
2653
2654 let stdout_task = stdout_pipe.map(|pipe| {
2655 tokio::spawn(async move {
2656 drain_to_stream(pipe, stdout_clone).await;
2657 })
2658 });
2659
2660 let stderr_task = stderr_pipe.map(|pipe| {
2661 tokio::spawn(async move {
2662 drain_to_stream(pipe, stderr_clone).await;
2663 })
2664 });
2665
2666 let status = match child.wait().await {
2667 Ok(s) => s,
2668 Err(e) => {
2669 return Ok(Some(ExecResult::failure(
2670 1,
2671 format!("{}: failed to wait: {}", name, e),
2672 )));
2673 }
2674 };
2675
2676 if let Some(task) = stdout_task {
2677 let _ = task.await;
2679 }
2680 if let Some(task) = stderr_task {
2681 let _ = task.await;
2682 }
2683
2684 let code = status.code().unwrap_or_else(|| {
2685 #[cfg(unix)]
2686 {
2687 use std::os::unix::process::ExitStatusExt;
2688 128 + status.signal().unwrap_or(0)
2689 }
2690 #[cfg(not(unix))]
2691 {
2692 -1
2693 }
2694 }) as i64;
2695
2696 let stdout = stdout_stream.read_string().await;
2697 let stderr = stderr_stream.read_string().await;
2698
2699 Ok(Some(ExecResult::from_output(code, stdout, stderr)))
2700 }
2701 }
2702
2703 pub async fn get_var(&self, name: &str) -> Option<Value> {
2707 let scope = self.scope.read().await;
2708 scope.get(name).cloned()
2709 }
2710
2711 #[cfg(test)]
2713 pub async fn error_exit_enabled(&self) -> bool {
2714 let scope = self.scope.read().await;
2715 scope.error_exit_enabled()
2716 }
2717
2718 pub async fn set_var(&self, name: &str, value: Value) {
2720 let mut scope = self.scope.write().await;
2721 scope.set(name.to_string(), value);
2722 }
2723
2724 pub async fn set_positional(&self, script_name: impl Into<String>, args: Vec<String>) {
2726 let mut scope = self.scope.write().await;
2727 scope.set_positional(script_name, args);
2728 }
2729
2730 pub async fn list_vars(&self) -> Vec<(String, Value)> {
2732 let scope = self.scope.read().await;
2733 scope.all()
2734 }
2735
2736 pub async fn cwd(&self) -> PathBuf {
2740 self.exec_ctx.read().await.cwd.clone()
2741 }
2742
2743 pub async fn set_cwd(&self, path: PathBuf) {
2745 let mut ctx = self.exec_ctx.write().await;
2746 ctx.set_cwd(path);
2747 }
2748
2749 pub async fn last_result(&self) -> ExecResult {
2753 let scope = self.scope.read().await;
2754 scope.last_result().clone()
2755 }
2756
2757 pub async fn has_function(&self, name: &str) -> bool {
2761 self.user_tools.read().await.contains_key(name)
2762 }
2763
2764 pub fn tool_schemas(&self) -> Vec<crate::tools::ToolSchema> {
2766 self.tools.schemas()
2767 }
2768
2769 pub fn jobs(&self) -> Arc<JobManager> {
2773 self.jobs.clone()
2774 }
2775
2776 pub fn vfs(&self) -> Arc<VfsRouter> {
2780 self.vfs.clone()
2781 }
2782
2783 pub async fn reset(&self) -> Result<()> {
2790 {
2791 let mut scope = self.scope.write().await;
2792 *scope = Scope::new();
2793 }
2794 {
2795 let mut ctx = self.exec_ctx.write().await;
2796 ctx.cwd = PathBuf::from("/");
2797 }
2798 Ok(())
2799 }
2800
2801 pub async fn shutdown(self) -> Result<()> {
2803 self.jobs.wait_all().await;
2805 Ok(())
2806 }
2807
2808 async fn dispatch_command(&self, cmd: &Command, ctx: &mut ExecContext) -> Result<ExecResult> {
2819 {
2821 let mut scope = self.scope.write().await;
2822 *scope = ctx.scope.clone();
2823 }
2824 {
2825 let mut ec = self.exec_ctx.write().await;
2826 ec.cwd = ctx.cwd.clone();
2827 ec.prev_cwd = ctx.prev_cwd.clone();
2828 ec.stdin = ctx.stdin.take();
2829 ec.stdin_data = ctx.stdin_data.take();
2830 ec.aliases = ctx.aliases.clone();
2831 ec.ignore_config = ctx.ignore_config.clone();
2832 ec.output_limit = ctx.output_limit.clone();
2833 ec.pipeline_position = ctx.pipeline_position;
2834 }
2835
2836 let result = self.execute_command(&cmd.name, &cmd.args).await?;
2838
2839 {
2841 let scope = self.scope.read().await;
2842 ctx.scope = scope.clone();
2843 }
2844 {
2845 let ec = self.exec_ctx.read().await;
2846 ctx.cwd = ec.cwd.clone();
2847 ctx.prev_cwd = ec.prev_cwd.clone();
2848 ctx.aliases = ec.aliases.clone();
2849 ctx.ignore_config = ec.ignore_config.clone();
2850 ctx.output_limit = ec.output_limit.clone();
2851 }
2852
2853 Ok(result)
2854 }
2855}
2856
2857#[async_trait]
2858impl CommandDispatcher for Kernel {
2859 async fn dispatch(&self, cmd: &Command, ctx: &mut ExecContext) -> Result<ExecResult> {
2865 self.dispatch_command(cmd, ctx).await
2866 }
2867}
2868
2869fn accumulate_result(accumulated: &mut ExecResult, new: &ExecResult) {
2875 if !accumulated.out.is_empty() && !new.out.is_empty() && !accumulated.out.ends_with('\n') {
2876 accumulated.out.push('\n');
2877 }
2878 accumulated.out.push_str(&new.out);
2879 if !accumulated.err.is_empty() && !new.err.is_empty() && !accumulated.err.ends_with('\n') {
2880 accumulated.err.push('\n');
2881 }
2882 accumulated.err.push_str(&new.err);
2883 accumulated.code = new.code;
2884 accumulated.data = new.data.clone();
2885}
2886
2887fn is_truthy(value: &Value) -> bool {
2889 match value {
2890 Value::Null => false,
2891 Value::Bool(b) => *b,
2892 Value::Int(i) => *i != 0,
2893 Value::Float(f) => *f != 0.0,
2894 Value::String(s) => !s.is_empty(),
2895 Value::Json(json) => match json {
2896 serde_json::Value::Null => false,
2897 serde_json::Value::Array(arr) => !arr.is_empty(),
2898 serde_json::Value::Object(obj) => !obj.is_empty(),
2899 serde_json::Value::Bool(b) => *b,
2900 serde_json::Value::Number(n) => n.as_f64().map(|f| f != 0.0).unwrap_or(false),
2901 serde_json::Value::String(s) => !s.is_empty(),
2902 },
2903 Value::Blob(_) => true, }
2905}
2906
2907fn apply_tilde_expansion(value: Value) -> Value {
2911 match value {
2912 Value::String(s) if s.starts_with('~') => Value::String(expand_tilde(&s)),
2913 _ => value,
2914 }
2915}
2916
2917#[cfg(test)]
2918mod tests {
2919 use super::*;
2920
2921 #[tokio::test]
2922 async fn test_kernel_transient() {
2923 let kernel = Kernel::transient().expect("failed to create kernel");
2924 assert_eq!(kernel.name(), "transient");
2925 }
2926
2927 #[tokio::test]
2928 async fn test_kernel_execute_echo() {
2929 let kernel = Kernel::transient().expect("failed to create kernel");
2930 let result = kernel.execute("echo hello").await.expect("execution failed");
2931 assert!(result.ok());
2932 assert_eq!(result.out.trim(), "hello");
2933 }
2934
2935 #[tokio::test]
2936 async fn test_multiple_statements_accumulate_output() {
2937 let kernel = Kernel::transient().expect("failed to create kernel");
2938 let result = kernel
2939 .execute("echo one\necho two\necho three")
2940 .await
2941 .expect("execution failed");
2942 assert!(result.ok());
2943 assert!(result.out.contains("one"), "missing 'one': {}", result.out);
2945 assert!(result.out.contains("two"), "missing 'two': {}", result.out);
2946 assert!(result.out.contains("three"), "missing 'three': {}", result.out);
2947 }
2948
2949 #[tokio::test]
2950 async fn test_and_chain_accumulates_output() {
2951 let kernel = Kernel::transient().expect("failed to create kernel");
2952 let result = kernel
2953 .execute("echo first && echo second")
2954 .await
2955 .expect("execution failed");
2956 assert!(result.ok());
2957 assert!(result.out.contains("first"), "missing 'first': {}", result.out);
2958 assert!(result.out.contains("second"), "missing 'second': {}", result.out);
2959 }
2960
2961 #[tokio::test]
2962 async fn test_for_loop_accumulates_output() {
2963 let kernel = Kernel::transient().expect("failed to create kernel");
2964 let result = kernel
2965 .execute(r#"for X in a b c; do echo "item: ${X}"; done"#)
2966 .await
2967 .expect("execution failed");
2968 assert!(result.ok());
2969 assert!(result.out.contains("item: a"), "missing 'item: a': {}", result.out);
2970 assert!(result.out.contains("item: b"), "missing 'item: b': {}", result.out);
2971 assert!(result.out.contains("item: c"), "missing 'item: c': {}", result.out);
2972 }
2973
2974 #[tokio::test]
2975 async fn test_while_loop_accumulates_output() {
2976 let kernel = Kernel::transient().expect("failed to create kernel");
2977 let result = kernel
2978 .execute(r#"
2979 N=3
2980 while [[ ${N} -gt 0 ]]; do
2981 echo "N=${N}"
2982 N=$((N - 1))
2983 done
2984 "#)
2985 .await
2986 .expect("execution failed");
2987 assert!(result.ok());
2988 assert!(result.out.contains("N=3"), "missing 'N=3': {}", result.out);
2989 assert!(result.out.contains("N=2"), "missing 'N=2': {}", result.out);
2990 assert!(result.out.contains("N=1"), "missing 'N=1': {}", result.out);
2991 }
2992
2993 #[tokio::test]
2994 async fn test_kernel_set_var() {
2995 let kernel = Kernel::transient().expect("failed to create kernel");
2996
2997 kernel.execute("X=42").await.expect("set failed");
2998
2999 let value = kernel.get_var("X").await;
3000 assert_eq!(value, Some(Value::Int(42)));
3001 }
3002
3003 #[tokio::test]
3004 async fn test_kernel_var_expansion() {
3005 let kernel = Kernel::transient().expect("failed to create kernel");
3006
3007 kernel.execute("NAME=\"world\"").await.expect("set failed");
3008 let result = kernel.execute("echo \"hello ${NAME}\"").await.expect("echo failed");
3009
3010 assert!(result.ok());
3011 assert_eq!(result.out.trim(), "hello world");
3012 }
3013
3014 #[tokio::test]
3015 async fn test_kernel_last_result() {
3016 let kernel = Kernel::transient().expect("failed to create kernel");
3017
3018 kernel.execute("echo test").await.expect("echo failed");
3019
3020 let last = kernel.last_result().await;
3021 assert!(last.ok());
3022 assert_eq!(last.out.trim(), "test");
3023 }
3024
3025 #[tokio::test]
3026 async fn test_kernel_tool_not_found() {
3027 let kernel = Kernel::transient().expect("failed to create kernel");
3028
3029 let result = kernel.execute("nonexistent_tool").await.expect("execution failed");
3030 assert!(!result.ok());
3031 assert_eq!(result.code, 127);
3032 assert!(result.err.contains("command not found"));
3033 }
3034
3035 #[tokio::test]
3036 async fn test_external_command_true() {
3037 let kernel = Kernel::new(KernelConfig::repl()).expect("failed to create kernel");
3039
3040 let result = kernel.execute("true").await.expect("execution failed");
3042 assert!(result.ok(), "true should succeed: {:?}", result);
3044 }
3045
3046 #[tokio::test]
3047 async fn test_external_command_basic() {
3048 let kernel = Kernel::new(KernelConfig::repl()).expect("failed to create kernel");
3050
3051 let path_var = std::env::var("PATH").unwrap_or_default();
3056 eprintln!("System PATH: {}", path_var);
3057
3058 kernel.execute(&format!(r#"PATH="{}""#, path_var)).await.expect("set PATH failed");
3060
3061 let result = kernel.execute("uname").await.expect("execution failed");
3064 eprintln!("uname result: {:?}", result);
3065 assert!(result.ok() || result.code == 127, "uname: {:?}", result);
3067 }
3068
3069 #[tokio::test]
3070 async fn test_kernel_reset() {
3071 let kernel = Kernel::transient().expect("failed to create kernel");
3072
3073 kernel.execute("X=1").await.expect("set failed");
3074 assert!(kernel.get_var("X").await.is_some());
3075
3076 kernel.reset().await.expect("reset failed");
3077 assert!(kernel.get_var("X").await.is_none());
3078 }
3079
3080 #[tokio::test]
3081 async fn test_kernel_cwd() {
3082 let kernel = Kernel::transient().expect("failed to create kernel");
3083
3084 let cwd = kernel.cwd().await;
3086 let home = std::env::var("HOME")
3087 .map(PathBuf::from)
3088 .unwrap_or_else(|_| PathBuf::from("/"));
3089 assert_eq!(cwd, home);
3090
3091 kernel.set_cwd(PathBuf::from("/tmp")).await;
3092 assert_eq!(kernel.cwd().await, PathBuf::from("/tmp"));
3093 }
3094
3095 #[tokio::test]
3096 async fn test_kernel_list_vars() {
3097 let kernel = Kernel::transient().expect("failed to create kernel");
3098
3099 kernel.execute("A=1").await.ok();
3100 kernel.execute("B=2").await.ok();
3101
3102 let vars = kernel.list_vars().await;
3103 assert!(vars.iter().any(|(n, v)| n == "A" && *v == Value::Int(1)));
3104 assert!(vars.iter().any(|(n, v)| n == "B" && *v == Value::Int(2)));
3105 }
3106
3107 #[tokio::test]
3108 async fn test_is_truthy() {
3109 assert!(!is_truthy(&Value::Null));
3110 assert!(!is_truthy(&Value::Bool(false)));
3111 assert!(is_truthy(&Value::Bool(true)));
3112 assert!(!is_truthy(&Value::Int(0)));
3113 assert!(is_truthy(&Value::Int(1)));
3114 assert!(!is_truthy(&Value::String("".into())));
3115 assert!(is_truthy(&Value::String("x".into())));
3116 }
3117
3118 #[tokio::test]
3119 async fn test_jq_in_pipeline() {
3120 let kernel = Kernel::transient().expect("failed to create kernel");
3121 let result = kernel
3123 .execute(r#"echo "{\"name\": \"Alice\"}" | jq ".name" -r"#)
3124 .await
3125 .expect("execution failed");
3126 assert!(result.ok(), "jq pipeline failed: {}", result.err);
3127 assert_eq!(result.out.trim(), "Alice");
3128 }
3129
3130 #[tokio::test]
3131 async fn test_user_defined_tool() {
3132 let kernel = Kernel::transient().expect("failed to create kernel");
3133
3134 kernel
3136 .execute(r#"greet() { echo "Hello, $1!" }"#)
3137 .await
3138 .expect("function definition failed");
3139
3140 let result = kernel
3142 .execute(r#"greet "World""#)
3143 .await
3144 .expect("function call failed");
3145
3146 assert!(result.ok(), "greet failed: {}", result.err);
3147 assert_eq!(result.out.trim(), "Hello, World!");
3148 }
3149
3150 #[tokio::test]
3151 async fn test_user_tool_positional_args() {
3152 let kernel = Kernel::transient().expect("failed to create kernel");
3153
3154 kernel
3156 .execute(r#"greet() { echo "Hi $1" }"#)
3157 .await
3158 .expect("function definition failed");
3159
3160 let result = kernel
3162 .execute(r#"greet "Amy""#)
3163 .await
3164 .expect("function call failed");
3165
3166 assert!(result.ok(), "greet failed: {}", result.err);
3167 assert_eq!(result.out.trim(), "Hi Amy");
3168 }
3169
3170 #[tokio::test]
3171 async fn test_function_shared_scope() {
3172 let kernel = Kernel::transient().expect("failed to create kernel");
3173
3174 kernel
3176 .execute(r#"SECRET="hidden""#)
3177 .await
3178 .expect("set failed");
3179
3180 kernel
3182 .execute(r#"access_parent() {
3183 echo "${SECRET}"
3184 SECRET="modified"
3185 }"#)
3186 .await
3187 .expect("function definition failed");
3188
3189 let result = kernel.execute("access_parent").await.expect("function call failed");
3191
3192 assert!(
3194 result.out.contains("hidden"),
3195 "Function should access parent scope, got: {}",
3196 result.out
3197 );
3198
3199 let secret = kernel.get_var("SECRET").await;
3201 assert_eq!(
3202 secret,
3203 Some(Value::String("modified".into())),
3204 "Function should modify parent scope"
3205 );
3206 }
3207
3208 #[tokio::test]
3209 async fn test_exec_builtin() {
3210 let kernel = Kernel::transient().expect("failed to create kernel");
3211 let result = kernel
3213 .execute(r#"exec command="/bin/echo" argv="hello world""#)
3214 .await
3215 .expect("exec failed");
3216
3217 assert!(result.ok(), "exec failed: {}", result.err);
3218 assert_eq!(result.out.trim(), "hello world");
3219 }
3220
3221 #[tokio::test]
3222 async fn test_while_false_never_runs() {
3223 let kernel = Kernel::transient().expect("failed to create kernel");
3224
3225 let result = kernel
3227 .execute(r#"
3228 while false; do
3229 echo "should not run"
3230 done
3231 "#)
3232 .await
3233 .expect("while false failed");
3234
3235 assert!(result.ok());
3236 assert!(result.out.is_empty(), "while false should not execute body: {}", result.out);
3237 }
3238
3239 #[tokio::test]
3240 async fn test_while_string_comparison() {
3241 let kernel = Kernel::transient().expect("failed to create kernel");
3242
3243 kernel.execute(r#"FLAG="go""#).await.expect("set failed");
3245
3246 let result = kernel
3249 .execute(r#"
3250 while [[ ${FLAG} == "go" ]]; do
3251 FLAG="stop"
3252 echo "running"
3253 done
3254 "#)
3255 .await
3256 .expect("while with string cmp failed");
3257
3258 assert!(result.ok());
3259 assert!(result.out.contains("running"), "should have run once: {}", result.out);
3260
3261 let flag = kernel.get_var("FLAG").await;
3263 assert_eq!(flag, Some(Value::String("stop".into())));
3264 }
3265
3266 #[tokio::test]
3267 async fn test_while_numeric_comparison() {
3268 let kernel = Kernel::transient().expect("failed to create kernel");
3269
3270 kernel.execute("N=5").await.expect("set failed");
3272
3273 let result = kernel
3275 .execute(r#"
3276 while [[ ${N} -gt 3 ]]; do
3277 N=3
3278 echo "N was greater"
3279 done
3280 "#)
3281 .await
3282 .expect("while with > failed");
3283
3284 assert!(result.ok());
3285 assert!(result.out.contains("N was greater"), "should have run once: {}", result.out);
3286 }
3287
3288 #[tokio::test]
3289 async fn test_break_in_while_loop() {
3290 let kernel = Kernel::transient().expect("failed to create kernel");
3291
3292 let result = kernel
3293 .execute(r#"
3294 I=0
3295 while true; do
3296 I=1
3297 echo "before break"
3298 break
3299 echo "after break"
3300 done
3301 "#)
3302 .await
3303 .expect("while with break failed");
3304
3305 assert!(result.ok());
3306 assert!(result.out.contains("before break"), "should see before break: {}", result.out);
3307 assert!(!result.out.contains("after break"), "should not see after break: {}", result.out);
3308
3309 let i = kernel.get_var("I").await;
3311 assert_eq!(i, Some(Value::Int(1)));
3312 }
3313
3314 #[tokio::test]
3315 async fn test_continue_in_while_loop() {
3316 let kernel = Kernel::transient().expect("failed to create kernel");
3317
3318 let result = kernel
3323 .execute(r#"
3324 STATE="start"
3325 AFTER_CONTINUE="no"
3326 while [[ ${STATE} != "done" ]]; do
3327 if [[ ${STATE} == "start" ]]; then
3328 STATE="middle"
3329 continue
3330 AFTER_CONTINUE="yes"
3331 fi
3332 if [[ ${STATE} == "middle" ]]; then
3333 STATE="done"
3334 fi
3335 done
3336 "#)
3337 .await
3338 .expect("while with continue failed");
3339
3340 assert!(result.ok());
3341
3342 let state = kernel.get_var("STATE").await;
3344 assert_eq!(state, Some(Value::String("done".into())));
3345
3346 let after = kernel.get_var("AFTER_CONTINUE").await;
3348 assert_eq!(after, Some(Value::String("no".into())));
3349 }
3350
3351 #[tokio::test]
3352 async fn test_break_with_level() {
3353 let kernel = Kernel::transient().expect("failed to create kernel");
3354
3355 let result = kernel
3360 .execute(r#"
3361 OUTER=0
3362 while true; do
3363 OUTER=1
3364 for X in "1 2"; do
3365 break 2
3366 done
3367 OUTER=2
3368 done
3369 "#)
3370 .await
3371 .expect("nested break failed");
3372
3373 assert!(result.ok());
3374
3375 let outer = kernel.get_var("OUTER").await;
3377 assert_eq!(outer, Some(Value::Int(1)), "break 2 should have skipped OUTER=2");
3378 }
3379
3380 #[tokio::test]
3381 async fn test_return_from_tool() {
3382 let kernel = Kernel::transient().expect("failed to create kernel");
3383
3384 kernel
3386 .execute(r#"early_return() {
3387 if [[ $1 == 1 ]]; then
3388 return 42
3389 fi
3390 echo "not returned"
3391 }"#)
3392 .await
3393 .expect("function definition failed");
3394
3395 let result = kernel
3398 .execute("early_return 1")
3399 .await
3400 .expect("function call failed");
3401
3402 assert_eq!(result.code, 42);
3404 assert!(result.out.is_empty());
3406 }
3407
3408 #[tokio::test]
3409 async fn test_return_without_value() {
3410 let kernel = Kernel::transient().expect("failed to create kernel");
3411
3412 kernel
3414 .execute(r#"early_exit() {
3415 if [[ $1 == "stop" ]]; then
3416 return
3417 fi
3418 echo "continued"
3419 }"#)
3420 .await
3421 .expect("function definition failed");
3422
3423 let result = kernel
3425 .execute(r#"early_exit "stop""#)
3426 .await
3427 .expect("function call failed");
3428
3429 assert!(result.ok());
3430 assert!(result.out.is_empty() || result.out.trim().is_empty());
3431 }
3432
3433 #[tokio::test]
3434 async fn test_exit_stops_execution() {
3435 let kernel = Kernel::transient().expect("failed to create kernel");
3436
3437 kernel
3439 .execute(r#"
3440 BEFORE="yes"
3441 exit 0
3442 AFTER="yes"
3443 "#)
3444 .await
3445 .expect("execution failed");
3446
3447 let before = kernel.get_var("BEFORE").await;
3449 assert_eq!(before, Some(Value::String("yes".into())));
3450
3451 let after = kernel.get_var("AFTER").await;
3452 assert!(after.is_none(), "AFTER should not be set after exit");
3453 }
3454
3455 #[tokio::test]
3456 async fn test_exit_with_code() {
3457 let kernel = Kernel::transient().expect("failed to create kernel");
3458
3459 let result = kernel
3461 .execute("exit 42")
3462 .await
3463 .expect("exit failed");
3464
3465 assert_eq!(result.code, 42);
3466 assert!(result.out.is_empty(), "exit should not produce stdout");
3467 }
3468
3469 #[tokio::test]
3470 async fn test_set_e_stops_on_failure() {
3471 let kernel = Kernel::transient().expect("failed to create kernel");
3472
3473 kernel.execute("set -e").await.expect("set -e failed");
3475
3476 kernel
3478 .execute(r#"
3479 STEP1="done"
3480 false
3481 STEP2="done"
3482 "#)
3483 .await
3484 .expect("execution failed");
3485
3486 let step1 = kernel.get_var("STEP1").await;
3488 assert_eq!(step1, Some(Value::String("done".into())));
3489
3490 let step2 = kernel.get_var("STEP2").await;
3491 assert!(step2.is_none(), "STEP2 should not be set after false with set -e");
3492 }
3493
3494 #[tokio::test]
3495 async fn test_set_plus_e_disables_error_exit() {
3496 let kernel = Kernel::transient().expect("failed to create kernel");
3497
3498 kernel.execute("set -e").await.expect("set -e failed");
3500 kernel.execute("set +e").await.expect("set +e failed");
3501
3502 kernel
3504 .execute(r#"
3505 STEP1="done"
3506 false
3507 STEP2="done"
3508 "#)
3509 .await
3510 .expect("execution failed");
3511
3512 let step1 = kernel.get_var("STEP1").await;
3514 assert_eq!(step1, Some(Value::String("done".into())));
3515
3516 let step2 = kernel.get_var("STEP2").await;
3517 assert_eq!(step2, Some(Value::String("done".into())));
3518 }
3519
3520 #[tokio::test]
3521 async fn test_set_ignores_unknown_options() {
3522 let kernel = Kernel::transient().expect("failed to create kernel");
3523
3524 let result = kernel
3526 .execute("set -e -u -o pipefail")
3527 .await
3528 .expect("set with unknown options failed");
3529
3530 assert!(result.ok(), "set should succeed with unknown options");
3531
3532 kernel
3534 .execute(r#"
3535 BEFORE="yes"
3536 false
3537 AFTER="yes"
3538 "#)
3539 .await
3540 .ok();
3541
3542 let after = kernel.get_var("AFTER").await;
3543 assert!(after.is_none(), "-e should be enabled despite unknown options");
3544 }
3545
3546 #[tokio::test]
3547 async fn test_set_no_args_shows_settings() {
3548 let kernel = Kernel::transient().expect("failed to create kernel");
3549
3550 kernel.execute("set -e").await.expect("set -e failed");
3552
3553 let result = kernel.execute("set").await.expect("set failed");
3555
3556 assert!(result.ok());
3557 assert!(result.out.contains("set -e"), "should show -e is enabled: {}", result.out);
3558 }
3559
3560 #[tokio::test]
3561 async fn test_set_e_in_pipeline() {
3562 let kernel = Kernel::transient().expect("failed to create kernel");
3563
3564 kernel.execute("set -e").await.expect("set -e failed");
3565
3566 kernel
3568 .execute(r#"
3569 BEFORE="yes"
3570 false | cat
3571 AFTER="yes"
3572 "#)
3573 .await
3574 .ok();
3575
3576 let before = kernel.get_var("BEFORE").await;
3577 assert_eq!(before, Some(Value::String("yes".into())));
3578
3579 }
3584
3585 #[tokio::test]
3586 async fn test_set_e_with_and_chain() {
3587 let kernel = Kernel::transient().expect("failed to create kernel");
3588
3589 kernel.execute("set -e").await.expect("set -e failed");
3590
3591 kernel
3594 .execute(r#"
3595 RESULT="initial"
3596 false && RESULT="chained"
3597 RESULT="continued"
3598 "#)
3599 .await
3600 .ok();
3601
3602 let result = kernel.get_var("RESULT").await;
3605 assert!(result.is_some(), "RESULT should be set");
3608 }
3609
3610 #[tokio::test]
3611 async fn test_set_e_exits_in_for_loop() {
3612 let kernel = Kernel::transient().expect("failed to create kernel");
3613
3614 kernel.execute("set -e").await.expect("set -e failed");
3615
3616 kernel
3617 .execute(r#"
3618 REACHED="no"
3619 for x in 1 2 3; do
3620 false
3621 REACHED="yes"
3622 done
3623 "#)
3624 .await
3625 .ok();
3626
3627 let reached = kernel.get_var("REACHED").await;
3629 assert_eq!(reached, Some(Value::String("no".into())),
3630 "set -e should exit on failure in for loop body");
3631 }
3632
3633 #[tokio::test]
3634 async fn test_for_loop_continues_without_set_e() {
3635 let kernel = Kernel::transient().expect("failed to create kernel");
3636
3637 kernel
3639 .execute(r#"
3640 COUNT=0
3641 for x in 1 2 3; do
3642 false
3643 COUNT=$((COUNT + 1))
3644 done
3645 "#)
3646 .await
3647 .ok();
3648
3649 let count = kernel.get_var("COUNT").await;
3650 let count_val = match &count {
3652 Some(Value::Int(n)) => *n,
3653 Some(Value::String(s)) => s.parse().unwrap_or(-1),
3654 _ => -1,
3655 };
3656 assert_eq!(count_val, 3,
3657 "without set -e, loop should complete all iterations (got {:?})", count);
3658 }
3659
3660 #[tokio::test]
3665 async fn test_source_sets_variables() {
3666 let kernel = Kernel::transient().expect("failed to create kernel");
3667
3668 kernel
3670 .execute(r#"write "/test.kai" 'FOO="bar"'"#)
3671 .await
3672 .expect("write failed");
3673
3674 let result = kernel
3676 .execute(r#"source "/test.kai""#)
3677 .await
3678 .expect("source failed");
3679
3680 assert!(result.ok(), "source should succeed");
3681
3682 let foo = kernel.get_var("FOO").await;
3684 assert_eq!(foo, Some(Value::String("bar".into())));
3685 }
3686
3687 #[tokio::test]
3688 async fn test_source_with_dot_alias() {
3689 let kernel = Kernel::transient().expect("failed to create kernel");
3690
3691 kernel
3693 .execute(r#"write "/vars.kai" 'X=42'"#)
3694 .await
3695 .expect("write failed");
3696
3697 let result = kernel
3699 .execute(r#". "/vars.kai""#)
3700 .await
3701 .expect(". failed");
3702
3703 assert!(result.ok(), ". should succeed");
3704
3705 let x = kernel.get_var("X").await;
3707 assert_eq!(x, Some(Value::Int(42)));
3708 }
3709
3710 #[tokio::test]
3711 async fn test_source_not_found() {
3712 let kernel = Kernel::transient().expect("failed to create kernel");
3713
3714 let result = kernel
3716 .execute(r#"source "/nonexistent.kai""#)
3717 .await
3718 .expect("source should not fail with error");
3719
3720 assert!(!result.ok(), "source of non-existent file should fail");
3721 assert!(result.err.contains("nonexistent.kai"), "error should mention filename");
3722 }
3723
3724 #[tokio::test]
3725 async fn test_source_missing_filename() {
3726 let kernel = Kernel::transient().expect("failed to create kernel");
3727
3728 let result = kernel
3730 .execute("source")
3731 .await
3732 .expect("source should not fail with error");
3733
3734 assert!(!result.ok(), "source without filename should fail");
3735 assert!(result.err.contains("missing filename"), "error should mention missing filename");
3736 }
3737
3738 #[tokio::test]
3739 async fn test_source_executes_multiple_statements() {
3740 let kernel = Kernel::transient().expect("failed to create kernel");
3741
3742 kernel
3744 .execute(r#"write "/multi.kai" 'A=1
3745B=2
3746C=3'"#)
3747 .await
3748 .expect("write failed");
3749
3750 kernel
3752 .execute(r#"source "/multi.kai""#)
3753 .await
3754 .expect("source failed");
3755
3756 assert_eq!(kernel.get_var("A").await, Some(Value::Int(1)));
3758 assert_eq!(kernel.get_var("B").await, Some(Value::Int(2)));
3759 assert_eq!(kernel.get_var("C").await, Some(Value::Int(3)));
3760 }
3761
3762 #[tokio::test]
3763 async fn test_source_can_define_functions() {
3764 let kernel = Kernel::transient().expect("failed to create kernel");
3765
3766 kernel
3768 .execute(r#"write "/functions.kai" 'greet() {
3769 echo "Hello, $1!"
3770}'"#)
3771 .await
3772 .expect("write failed");
3773
3774 kernel
3776 .execute(r#"source "/functions.kai""#)
3777 .await
3778 .expect("source failed");
3779
3780 let result = kernel
3782 .execute(r#"greet "World""#)
3783 .await
3784 .expect("greet failed");
3785
3786 assert!(result.ok());
3787 assert!(result.out.contains("Hello, World!"));
3788 }
3789
3790 #[tokio::test]
3791 async fn test_source_inherits_error_exit() {
3792 let kernel = Kernel::transient().expect("failed to create kernel");
3793
3794 kernel.execute("set -e").await.expect("set -e failed");
3796
3797 kernel
3799 .execute(r#"write "/fail.kai" 'BEFORE="yes"
3800false
3801AFTER="yes"'"#)
3802 .await
3803 .expect("write failed");
3804
3805 kernel
3807 .execute(r#"source "/fail.kai""#)
3808 .await
3809 .ok();
3810
3811 let before = kernel.get_var("BEFORE").await;
3813 assert_eq!(before, Some(Value::String("yes".into())));
3814
3815 }
3818
3819 #[tokio::test]
3824 async fn test_set_e_and_chain_left_fails() {
3825 let kernel = Kernel::transient().expect("failed to create kernel");
3827 kernel.execute("set -e").await.expect("set -e failed");
3828
3829 kernel
3830 .execute("false && echo hi; REACHED=1")
3831 .await
3832 .expect("execution failed");
3833
3834 let reached = kernel.get_var("REACHED").await;
3835 assert_eq!(
3836 reached,
3837 Some(Value::Int(1)),
3838 "set -e should not trigger on left side of &&"
3839 );
3840 }
3841
3842 #[tokio::test]
3843 async fn test_set_e_and_chain_right_fails() {
3844 let kernel = Kernel::transient().expect("failed to create kernel");
3846 kernel.execute("set -e").await.expect("set -e failed");
3847
3848 kernel
3849 .execute("true && false; REACHED=1")
3850 .await
3851 .expect("execution failed");
3852
3853 let reached = kernel.get_var("REACHED").await;
3854 assert!(
3855 reached.is_none(),
3856 "set -e should trigger when right side of && fails"
3857 );
3858 }
3859
3860 #[tokio::test]
3861 async fn test_set_e_or_chain_recovers() {
3862 let kernel = Kernel::transient().expect("failed to create kernel");
3864 kernel.execute("set -e").await.expect("set -e failed");
3865
3866 kernel
3867 .execute("false || echo recovered; REACHED=1")
3868 .await
3869 .expect("execution failed");
3870
3871 let reached = kernel.get_var("REACHED").await;
3872 assert_eq!(
3873 reached,
3874 Some(Value::Int(1)),
3875 "set -e should not trigger when || recovers the failure"
3876 );
3877 }
3878
3879 #[tokio::test]
3880 async fn test_set_e_or_chain_both_fail() {
3881 let kernel = Kernel::transient().expect("failed to create kernel");
3883 kernel.execute("set -e").await.expect("set -e failed");
3884
3885 kernel
3886 .execute("false || false; REACHED=1")
3887 .await
3888 .expect("execution failed");
3889
3890 let reached = kernel.get_var("REACHED").await;
3891 assert!(
3892 reached.is_none(),
3893 "set -e should trigger when || chain ultimately fails"
3894 );
3895 }
3896
3897 fn schedule_cancel(kernel: &Arc<Kernel>, delay: std::time::Duration) {
3904 let k = Arc::clone(kernel);
3905 std::thread::spawn(move || {
3906 std::thread::sleep(delay);
3907 k.cancel();
3908 });
3909 }
3910
3911 #[tokio::test]
3912 async fn test_cancel_interrupts_for_loop() {
3913 let kernel = Arc::new(Kernel::transient().expect("failed to create kernel"));
3914
3915 schedule_cancel(&kernel, std::time::Duration::from_millis(10));
3917
3918 let result = kernel
3919 .execute("for i in $(seq 1 100000); do X=$i; done")
3920 .await
3921 .expect("execute failed");
3922
3923 assert_eq!(result.code, 130, "cancelled execution should exit with code 130");
3924
3925 let x = kernel.get_var("X").await;
3927 if let Some(Value::Int(n)) = x {
3928 assert!(n < 100000, "loop should have been interrupted before finishing, got X={n}");
3929 }
3930 }
3931
3932 #[tokio::test]
3933 async fn test_cancel_interrupts_while_loop() {
3934 let kernel = Arc::new(Kernel::transient().expect("failed to create kernel"));
3935 kernel.execute("COUNT=0").await.expect("init failed");
3936
3937 schedule_cancel(&kernel, std::time::Duration::from_millis(10));
3938
3939 let result = kernel
3940 .execute("while true; do COUNT=$((COUNT + 1)); done")
3941 .await
3942 .expect("execute failed");
3943
3944 assert_eq!(result.code, 130);
3945
3946 let count = kernel.get_var("COUNT").await;
3947 if let Some(Value::Int(n)) = count {
3948 assert!(n > 0, "loop should have run at least once");
3949 }
3950 }
3951
3952 #[tokio::test]
3953 async fn test_reset_after_cancel() {
3954 let kernel = Kernel::transient().expect("failed to create kernel");
3956 kernel.cancel(); let result = kernel.execute("echo hello").await.expect("execute failed");
3959 assert!(result.ok(), "execute after cancel should succeed");
3960 assert_eq!(result.out.trim(), "hello");
3961 }
3962
3963 #[tokio::test]
3964 async fn test_cancel_interrupts_statement_sequence() {
3965 let kernel = Arc::new(Kernel::transient().expect("failed to create kernel"));
3966
3967 schedule_cancel(&kernel, std::time::Duration::from_millis(50));
3969
3970 let result = kernel
3971 .execute("STEP=1; sleep 5; STEP=2; sleep 5; STEP=3")
3972 .await
3973 .expect("execute failed");
3974
3975 assert_eq!(result.code, 130);
3976
3977 let step = kernel.get_var("STEP").await;
3979 assert_eq!(step, Some(Value::Int(1)), "cancel should stop before STEP=2");
3980 }
3981
3982 #[tokio::test]
3987 async fn test_case_simple_match() {
3988 let kernel = Kernel::transient().expect("failed to create kernel");
3989
3990 let result = kernel
3991 .execute(r#"
3992 case "hello" in
3993 hello) echo "matched hello" ;;
3994 world) echo "matched world" ;;
3995 esac
3996 "#)
3997 .await
3998 .expect("case failed");
3999
4000 assert!(result.ok());
4001 assert_eq!(result.out.trim(), "matched hello");
4002 }
4003
4004 #[tokio::test]
4005 async fn test_case_wildcard_match() {
4006 let kernel = Kernel::transient().expect("failed to create kernel");
4007
4008 let result = kernel
4009 .execute(r#"
4010 case "main.rs" in
4011 "*.py") echo "Python" ;;
4012 "*.rs") echo "Rust" ;;
4013 "*") echo "Unknown" ;;
4014 esac
4015 "#)
4016 .await
4017 .expect("case failed");
4018
4019 assert!(result.ok());
4020 assert_eq!(result.out.trim(), "Rust");
4021 }
4022
4023 #[tokio::test]
4024 async fn test_case_default_match() {
4025 let kernel = Kernel::transient().expect("failed to create kernel");
4026
4027 let result = kernel
4028 .execute(r#"
4029 case "unknown.xyz" in
4030 "*.py") echo "Python" ;;
4031 "*.rs") echo "Rust" ;;
4032 "*") echo "Default" ;;
4033 esac
4034 "#)
4035 .await
4036 .expect("case failed");
4037
4038 assert!(result.ok());
4039 assert_eq!(result.out.trim(), "Default");
4040 }
4041
4042 #[tokio::test]
4043 async fn test_case_no_match() {
4044 let kernel = Kernel::transient().expect("failed to create kernel");
4045
4046 let result = kernel
4048 .execute(r#"
4049 case "nope" in
4050 "yes") echo "yes" ;;
4051 "no") echo "no" ;;
4052 esac
4053 "#)
4054 .await
4055 .expect("case failed");
4056
4057 assert!(result.ok());
4058 assert!(result.out.is_empty(), "no match should produce empty output");
4059 }
4060
4061 #[tokio::test]
4062 async fn test_case_with_variable() {
4063 let kernel = Kernel::transient().expect("failed to create kernel");
4064
4065 kernel.execute(r#"LANG="rust""#).await.expect("set failed");
4066
4067 let result = kernel
4068 .execute(r#"
4069 case ${LANG} in
4070 python) echo "snake" ;;
4071 rust) echo "crab" ;;
4072 go) echo "gopher" ;;
4073 esac
4074 "#)
4075 .await
4076 .expect("case failed");
4077
4078 assert!(result.ok());
4079 assert_eq!(result.out.trim(), "crab");
4080 }
4081
4082 #[tokio::test]
4083 async fn test_case_multiple_patterns() {
4084 let kernel = Kernel::transient().expect("failed to create kernel");
4085
4086 let result = kernel
4087 .execute(r#"
4088 case "yes" in
4089 "y"|"yes"|"Y"|"YES") echo "affirmative" ;;
4090 "n"|"no"|"N"|"NO") echo "negative" ;;
4091 esac
4092 "#)
4093 .await
4094 .expect("case failed");
4095
4096 assert!(result.ok());
4097 assert_eq!(result.out.trim(), "affirmative");
4098 }
4099
4100 #[tokio::test]
4101 async fn test_case_glob_question_mark() {
4102 let kernel = Kernel::transient().expect("failed to create kernel");
4103
4104 let result = kernel
4105 .execute(r#"
4106 case "test1" in
4107 "test?") echo "matched test?" ;;
4108 "*") echo "default" ;;
4109 esac
4110 "#)
4111 .await
4112 .expect("case failed");
4113
4114 assert!(result.ok());
4115 assert_eq!(result.out.trim(), "matched test?");
4116 }
4117
4118 #[tokio::test]
4119 async fn test_case_char_class() {
4120 let kernel = Kernel::transient().expect("failed to create kernel");
4121
4122 let result = kernel
4123 .execute(r#"
4124 case "Yes" in
4125 "[Yy]*") echo "yes-like" ;;
4126 "[Nn]*") echo "no-like" ;;
4127 esac
4128 "#)
4129 .await
4130 .expect("case failed");
4131
4132 assert!(result.ok());
4133 assert_eq!(result.out.trim(), "yes-like");
4134 }
4135
4136 #[tokio::test]
4141 async fn test_cat_from_pipeline() {
4142 let kernel = Kernel::transient().expect("failed to create kernel");
4143
4144 let result = kernel
4145 .execute(r#"echo "piped text" | cat"#)
4146 .await
4147 .expect("cat pipeline failed");
4148
4149 assert!(result.ok(), "cat failed: {}", result.err);
4150 assert_eq!(result.out.trim(), "piped text");
4151 }
4152
4153 #[tokio::test]
4154 async fn test_cat_from_pipeline_multiline() {
4155 let kernel = Kernel::transient().expect("failed to create kernel");
4156
4157 let result = kernel
4158 .execute(r#"echo "line1\nline2" | cat -n"#)
4159 .await
4160 .expect("cat pipeline failed");
4161
4162 assert!(result.ok(), "cat failed: {}", result.err);
4163 assert!(result.out.contains("1\t"), "output: {}", result.out);
4164 }
4165
4166 #[tokio::test]
4171 async fn test_heredoc_basic() {
4172 let kernel = Kernel::transient().expect("failed to create kernel");
4173
4174 let result = kernel
4175 .execute("cat <<EOF\nhello\nEOF")
4176 .await
4177 .expect("heredoc failed");
4178
4179 assert!(result.ok(), "cat with heredoc failed: {}", result.err);
4180 assert_eq!(result.out.trim(), "hello");
4181 }
4182
4183 #[tokio::test]
4184 async fn test_arithmetic_in_string() {
4185 let kernel = Kernel::transient().expect("failed to create kernel");
4186
4187 let result = kernel
4188 .execute(r#"echo "result: $((1 + 2))""#)
4189 .await
4190 .expect("arithmetic in string failed");
4191
4192 assert!(result.ok(), "echo failed: {}", result.err);
4193 assert_eq!(result.out.trim(), "result: 3");
4194 }
4195
4196 #[tokio::test]
4197 async fn test_heredoc_multiline() {
4198 let kernel = Kernel::transient().expect("failed to create kernel");
4199
4200 let result = kernel
4201 .execute("cat <<EOF\nline1\nline2\nline3\nEOF")
4202 .await
4203 .expect("heredoc failed");
4204
4205 assert!(result.ok(), "cat with heredoc failed: {}", result.err);
4206 assert!(result.out.contains("line1"), "output: {}", result.out);
4207 assert!(result.out.contains("line2"), "output: {}", result.out);
4208 assert!(result.out.contains("line3"), "output: {}", result.out);
4209 }
4210
4211 #[tokio::test]
4212 async fn test_heredoc_variable_expansion() {
4213 let kernel = Kernel::transient().expect("failed to create kernel");
4215
4216 kernel.execute("GREETING=hello").await.expect("set var");
4217
4218 let result = kernel
4219 .execute("cat <<EOF\n$GREETING world\nEOF")
4220 .await
4221 .expect("heredoc expansion failed");
4222
4223 assert!(result.ok(), "heredoc expansion failed: {}", result.err);
4224 assert_eq!(result.out.trim(), "hello world");
4225 }
4226
4227 #[tokio::test]
4228 async fn test_heredoc_quoted_no_expansion() {
4229 let kernel = Kernel::transient().expect("failed to create kernel");
4231
4232 kernel.execute("GREETING=hello").await.expect("set var");
4233
4234 let result = kernel
4235 .execute("cat <<'EOF'\n$GREETING world\nEOF")
4236 .await
4237 .expect("quoted heredoc failed");
4238
4239 assert!(result.ok(), "quoted heredoc failed: {}", result.err);
4240 assert_eq!(result.out.trim(), "$GREETING world");
4241 }
4242
4243 #[tokio::test]
4244 async fn test_heredoc_default_value_expansion() {
4245 let kernel = Kernel::transient().expect("failed to create kernel");
4247
4248 let result = kernel
4249 .execute("cat <<EOF\n${UNSET:-fallback}\nEOF")
4250 .await
4251 .expect("heredoc default expansion failed");
4252
4253 assert!(result.ok(), "heredoc default expansion failed: {}", result.err);
4254 assert_eq!(result.out.trim(), "fallback");
4255 }
4256
4257 #[tokio::test]
4262 async fn test_read_from_pipeline() {
4263 let kernel = Kernel::transient().expect("failed to create kernel");
4264
4265 let result = kernel
4267 .execute(r#"echo "Alice" | read NAME; echo "Hello, ${NAME}""#)
4268 .await
4269 .expect("read pipeline failed");
4270
4271 assert!(result.ok(), "read failed: {}", result.err);
4272 assert!(result.out.contains("Hello, Alice"), "output: {}", result.out);
4273 }
4274
4275 #[tokio::test]
4276 async fn test_read_multiple_vars_from_pipeline() {
4277 let kernel = Kernel::transient().expect("failed to create kernel");
4278
4279 let result = kernel
4280 .execute(r#"echo "John Doe 42" | read FIRST LAST AGE; echo "${FIRST} is ${AGE}""#)
4281 .await
4282 .expect("read pipeline failed");
4283
4284 assert!(result.ok(), "read failed: {}", result.err);
4285 assert!(result.out.contains("John is 42"), "output: {}", result.out);
4286 }
4287
4288 #[tokio::test]
4293 async fn test_posix_function_with_positional_params() {
4294 let kernel = Kernel::transient().expect("failed to create kernel");
4295
4296 kernel
4298 .execute(r#"greet() { echo "Hello, $1!" }"#)
4299 .await
4300 .expect("function definition failed");
4301
4302 let result = kernel
4304 .execute(r#"greet "Amy""#)
4305 .await
4306 .expect("function call failed");
4307
4308 assert!(result.ok(), "greet failed: {}", result.err);
4309 assert_eq!(result.out.trim(), "Hello, Amy!");
4310 }
4311
4312 #[tokio::test]
4313 async fn test_posix_function_multiple_args() {
4314 let kernel = Kernel::transient().expect("failed to create kernel");
4315
4316 kernel
4318 .execute(r#"add_greeting() { echo "$1 $2!" }"#)
4319 .await
4320 .expect("function definition failed");
4321
4322 let result = kernel
4324 .execute(r#"add_greeting "Hello" "World""#)
4325 .await
4326 .expect("function call failed");
4327
4328 assert!(result.ok(), "function failed: {}", result.err);
4329 assert_eq!(result.out.trim(), "Hello World!");
4330 }
4331
4332 #[tokio::test]
4333 async fn test_bash_function_with_positional_params() {
4334 let kernel = Kernel::transient().expect("failed to create kernel");
4335
4336 kernel
4338 .execute(r#"function greet { echo "Hi $1" }"#)
4339 .await
4340 .expect("function definition failed");
4341
4342 let result = kernel
4344 .execute(r#"greet "Bob""#)
4345 .await
4346 .expect("function call failed");
4347
4348 assert!(result.ok(), "greet failed: {}", result.err);
4349 assert_eq!(result.out.trim(), "Hi Bob");
4350 }
4351
4352 #[tokio::test]
4353 async fn test_shell_function_with_all_args() {
4354 let kernel = Kernel::transient().expect("failed to create kernel");
4355
4356 kernel
4358 .execute(r#"echo_all() { echo "args: $@" }"#)
4359 .await
4360 .expect("function definition failed");
4361
4362 let result = kernel
4364 .execute(r#"echo_all "a" "b" "c""#)
4365 .await
4366 .expect("function call failed");
4367
4368 assert!(result.ok(), "function failed: {}", result.err);
4369 assert_eq!(result.out.trim(), "args: a b c");
4370 }
4371
4372 #[tokio::test]
4373 async fn test_shell_function_with_arg_count() {
4374 let kernel = Kernel::transient().expect("failed to create kernel");
4375
4376 kernel
4378 .execute(r#"count_args() { echo "count: $#" }"#)
4379 .await
4380 .expect("function definition failed");
4381
4382 let result = kernel
4384 .execute(r#"count_args "x" "y" "z""#)
4385 .await
4386 .expect("function call failed");
4387
4388 assert!(result.ok(), "function failed: {}", result.err);
4389 assert_eq!(result.out.trim(), "count: 3");
4390 }
4391
4392 #[tokio::test]
4393 async fn test_shell_function_shared_scope() {
4394 let kernel = Kernel::transient().expect("failed to create kernel");
4395
4396 kernel
4398 .execute(r#"PARENT_VAR="visible""#)
4399 .await
4400 .expect("set failed");
4401
4402 kernel
4404 .execute(r#"modify_parent() {
4405 echo "saw: ${PARENT_VAR}"
4406 PARENT_VAR="changed by function"
4407 }"#)
4408 .await
4409 .expect("function definition failed");
4410
4411 let result = kernel.execute("modify_parent").await.expect("function failed");
4413
4414 assert!(
4415 result.out.contains("visible"),
4416 "Shell function should access parent scope, got: {}",
4417 result.out
4418 );
4419
4420 let var = kernel.get_var("PARENT_VAR").await;
4422 assert_eq!(
4423 var,
4424 Some(Value::String("changed by function".into())),
4425 "Shell function should modify parent scope"
4426 );
4427 }
4428
4429 #[tokio::test]
4434 async fn test_script_execution_from_path() {
4435 let kernel = Kernel::transient().expect("failed to create kernel");
4436
4437 kernel.execute(r#"mkdir "/bin""#).await.ok();
4439 kernel
4440 .execute(r#"write "/bin/hello.kai" 'echo "Hello from script!"'"#)
4441 .await
4442 .expect("write script failed");
4443
4444 kernel.execute(r#"PATH="/bin""#).await.expect("set PATH failed");
4446
4447 let result = kernel
4449 .execute("hello")
4450 .await
4451 .expect("script execution failed");
4452
4453 assert!(result.ok(), "script failed: {}", result.err);
4454 assert_eq!(result.out.trim(), "Hello from script!");
4455 }
4456
4457 #[tokio::test]
4458 async fn test_script_with_args() {
4459 let kernel = Kernel::transient().expect("failed to create kernel");
4460
4461 kernel.execute(r#"mkdir "/bin""#).await.ok();
4463 kernel
4464 .execute(r#"write "/bin/greet.kai" 'echo "Hello, $1!"'"#)
4465 .await
4466 .expect("write script failed");
4467
4468 kernel.execute(r#"PATH="/bin""#).await.expect("set PATH failed");
4470
4471 let result = kernel
4473 .execute(r#"greet "World""#)
4474 .await
4475 .expect("script execution failed");
4476
4477 assert!(result.ok(), "script failed: {}", result.err);
4478 assert_eq!(result.out.trim(), "Hello, World!");
4479 }
4480
4481 #[tokio::test]
4482 async fn test_script_not_found() {
4483 let kernel = Kernel::transient().expect("failed to create kernel");
4484
4485 kernel.execute(r#"PATH="/nonexistent""#).await.expect("set PATH failed");
4487
4488 let result = kernel
4490 .execute("noscript")
4491 .await
4492 .expect("execution failed");
4493
4494 assert!(!result.ok(), "should fail with command not found");
4495 assert_eq!(result.code, 127);
4496 assert!(result.err.contains("command not found"));
4497 }
4498
4499 #[tokio::test]
4500 async fn test_script_path_search_order() {
4501 let kernel = Kernel::transient().expect("failed to create kernel");
4502
4503 kernel.execute(r#"mkdir "/first""#).await.ok();
4506 kernel.execute(r#"mkdir "/second""#).await.ok();
4507 kernel
4508 .execute(r#"write "/first/myscript.kai" 'echo "from first"'"#)
4509 .await
4510 .expect("write failed");
4511 kernel
4512 .execute(r#"write "/second/myscript.kai" 'echo "from second"'"#)
4513 .await
4514 .expect("write failed");
4515
4516 kernel.execute(r#"PATH="/first:/second""#).await.expect("set PATH failed");
4518
4519 let result = kernel
4521 .execute("myscript")
4522 .await
4523 .expect("script execution failed");
4524
4525 assert!(result.ok(), "script failed: {}", result.err);
4526 assert_eq!(result.out.trim(), "from first");
4527 }
4528
4529 #[tokio::test]
4534 async fn test_last_exit_code_success() {
4535 let kernel = Kernel::transient().expect("failed to create kernel");
4536
4537 let result = kernel.execute("true; echo $?").await.expect("execution failed");
4539 assert!(result.out.contains("0"), "expected 0, got: {}", result.out);
4540 }
4541
4542 #[tokio::test]
4543 async fn test_last_exit_code_failure() {
4544 let kernel = Kernel::transient().expect("failed to create kernel");
4545
4546 let result = kernel.execute("false; echo $?").await.expect("execution failed");
4548 assert!(result.out.contains("1"), "expected 1, got: {}", result.out);
4549 }
4550
4551 #[tokio::test]
4552 async fn test_current_pid() {
4553 let kernel = Kernel::transient().expect("failed to create kernel");
4554
4555 let result = kernel.execute("echo $$").await.expect("execution failed");
4556 let pid: u32 = result.out.trim().parse().expect("PID should be a number");
4558 assert!(pid > 0, "PID should be positive");
4559 }
4560
4561 #[tokio::test]
4562 async fn test_unset_variable_expands_to_empty() {
4563 let kernel = Kernel::transient().expect("failed to create kernel");
4564
4565 let result = kernel.execute(r#"echo "prefix:${UNSET_VAR}:suffix""#).await.expect("execution failed");
4567 assert_eq!(result.out.trim(), "prefix::suffix");
4568 }
4569
4570 #[tokio::test]
4571 async fn test_eq_ne_operators() {
4572 let kernel = Kernel::transient().expect("failed to create kernel");
4573
4574 let result = kernel.execute(r#"if [[ 5 -eq 5 ]]; then echo "eq works"; fi"#).await.expect("execution failed");
4576 assert_eq!(result.out.trim(), "eq works");
4577
4578 let result = kernel.execute(r#"if [[ 5 -ne 3 ]]; then echo "ne works"; fi"#).await.expect("execution failed");
4580 assert_eq!(result.out.trim(), "ne works");
4581
4582 let result = kernel.execute(r#"if [[ 5 -eq 3 ]]; then echo "wrong"; else echo "correct"; fi"#).await.expect("execution failed");
4584 assert_eq!(result.out.trim(), "correct");
4585 }
4586
4587 #[tokio::test]
4588 async fn test_escaped_dollar_in_string() {
4589 let kernel = Kernel::transient().expect("failed to create kernel");
4590
4591 let result = kernel.execute(r#"echo "\$100""#).await.expect("execution failed");
4593 assert_eq!(result.out.trim(), "$100");
4594 }
4595
4596 #[tokio::test]
4597 async fn test_special_vars_in_interpolation() {
4598 let kernel = Kernel::transient().expect("failed to create kernel");
4599
4600 let result = kernel.execute(r#"true; echo "exit: $?""#).await.expect("execution failed");
4602 assert_eq!(result.out.trim(), "exit: 0");
4603
4604 let result = kernel.execute(r#"echo "pid: $$""#).await.expect("execution failed");
4606 assert!(result.out.starts_with("pid: "), "unexpected output: {}", result.out);
4607 let pid_part = result.out.trim().strip_prefix("pid: ").unwrap();
4608 let _pid: u32 = pid_part.parse().expect("PID in string should be a number");
4609 }
4610
4611 #[tokio::test]
4616 async fn test_command_subst_assignment() {
4617 let kernel = Kernel::transient().expect("failed to create kernel");
4618
4619 let result = kernel.execute(r#"X=$(echo hello); echo "$X""#).await.expect("execution failed");
4621 assert_eq!(result.out.trim(), "hello");
4622 }
4623
4624 #[tokio::test]
4625 async fn test_command_subst_with_args() {
4626 let kernel = Kernel::transient().expect("failed to create kernel");
4627
4628 let result = kernel.execute(r#"X=$(echo "a b c"); echo "$X""#).await.expect("execution failed");
4630 assert_eq!(result.out.trim(), "a b c");
4631 }
4632
4633 #[tokio::test]
4634 async fn test_command_subst_nested_vars() {
4635 let kernel = Kernel::transient().expect("failed to create kernel");
4636
4637 let result = kernel.execute(r#"Y=world; X=$(echo "hello $Y"); echo "$X""#).await.expect("execution failed");
4639 assert_eq!(result.out.trim(), "hello world");
4640 }
4641
4642 #[tokio::test]
4643 async fn test_background_job_basic() {
4644 use std::time::Duration;
4645
4646 let kernel = Kernel::new(KernelConfig::isolated()).expect("failed to create kernel");
4647
4648 let result = kernel.execute("echo hello &").await.expect("execution failed");
4650 assert!(result.ok(), "background command should succeed: {}", result.err);
4651 assert!(result.out.contains("[1]"), "should return job ID: {}", result.out);
4652
4653 tokio::time::sleep(Duration::from_millis(100)).await;
4655
4656 let status = kernel.execute("cat /v/jobs/1/status").await.expect("status check failed");
4658 assert!(status.ok(), "status should succeed: {}", status.err);
4659 assert!(
4660 status.out.contains("done:") || status.out.contains("running"),
4661 "should have valid status: {}",
4662 status.out
4663 );
4664
4665 let stdout = kernel.execute("cat /v/jobs/1/stdout").await.expect("stdout check failed");
4667 assert!(stdout.ok());
4668 assert!(stdout.out.contains("hello"));
4669 }
4670
4671 #[tokio::test]
4672 async fn test_heredoc_piped_to_command() {
4673 let kernel = Kernel::transient().expect("kernel");
4675 let result = kernel.execute("cat <<EOF | cat\nhello world\nEOF").await.expect("exec");
4676 assert!(result.ok(), "heredoc | cat failed: {}", result.err);
4677 assert_eq!(result.out.trim(), "hello world");
4678 }
4679
4680 #[tokio::test]
4681 async fn test_for_loop_glob_iterates() {
4682 let kernel = Kernel::transient().expect("kernel");
4684 let dir = format!("/tmp/kaish_test_glob_{}", std::process::id());
4685 kernel.execute(&format!("mkdir -p {dir}")).await.unwrap();
4686 kernel.execute(&format!("echo a > {dir}/a.txt")).await.unwrap();
4687 kernel.execute(&format!("echo b > {dir}/b.txt")).await.unwrap();
4688 let result = kernel.execute(&format!(r#"
4689 N=0
4690 for F in $(glob "{dir}/*.txt"); do
4691 N=$((N + 1))
4692 done
4693 echo $N
4694 "#)).await.unwrap();
4695 assert!(result.ok(), "for glob failed: {}", result.err);
4696 assert_eq!(result.out.trim(), "2", "Should iterate 2 files, got: {}", result.out);
4697 kernel.execute(&format!("rm {dir}/a.txt")).await.unwrap();
4698 kernel.execute(&format!("rm {dir}/b.txt")).await.unwrap();
4699 }
4700
4701 #[tokio::test]
4702 async fn test_command_subst_echo_not_iterable() {
4703 let kernel = Kernel::transient().expect("kernel");
4705 let result = kernel.execute(r#"
4706 N=0
4707 for X in $(echo "a b c"); do N=$((N + 1)); done
4708 echo $N
4709 "#).await.unwrap();
4710 assert!(result.ok());
4711 assert_eq!(result.out.trim(), "1", "echo should be one item: {}", result.out);
4712 }
4713
4714 #[test]
4717 fn test_accumulate_no_double_newlines() {
4718 let mut acc = ExecResult::success("line1\n");
4720 let new = ExecResult::success("line2\n");
4721 accumulate_result(&mut acc, &new);
4722 assert_eq!(acc.out, "line1\nline2\n");
4723 assert!(!acc.out.contains("\n\n"), "should not have double newlines: {:?}", acc.out);
4724 }
4725
4726 #[test]
4727 fn test_accumulate_adds_separator_when_needed() {
4728 let mut acc = ExecResult::success("line1");
4730 let new = ExecResult::success("line2");
4731 accumulate_result(&mut acc, &new);
4732 assert_eq!(acc.out, "line1\nline2");
4733 }
4734
4735 #[test]
4736 fn test_accumulate_empty_into_nonempty() {
4737 let mut acc = ExecResult::success("");
4738 let new = ExecResult::success("hello\n");
4739 accumulate_result(&mut acc, &new);
4740 assert_eq!(acc.out, "hello\n");
4741 }
4742
4743 #[test]
4744 fn test_accumulate_nonempty_into_empty() {
4745 let mut acc = ExecResult::success("hello\n");
4746 let new = ExecResult::success("");
4747 accumulate_result(&mut acc, &new);
4748 assert_eq!(acc.out, "hello\n");
4749 }
4750
4751 #[test]
4752 fn test_accumulate_stderr_no_double_newlines() {
4753 let mut acc = ExecResult::failure(1, "err1\n");
4754 let new = ExecResult::failure(1, "err2\n");
4755 accumulate_result(&mut acc, &new);
4756 assert!(!acc.err.contains("\n\n"), "stderr should not have double newlines: {:?}", acc.err);
4757 }
4758
4759 #[tokio::test]
4760 async fn test_multiple_echo_no_blank_lines() {
4761 let kernel = Kernel::transient().expect("kernel");
4762 let result = kernel
4763 .execute("echo one\necho two\necho three")
4764 .await
4765 .expect("execution failed");
4766 assert!(result.ok());
4767 assert_eq!(result.out, "one\ntwo\nthree\n");
4768 }
4769
4770 #[tokio::test]
4771 async fn test_for_loop_no_blank_lines() {
4772 let kernel = Kernel::transient().expect("kernel");
4773 let result = kernel
4774 .execute(r#"for X in a b c; do echo "item: ${X}"; done"#)
4775 .await
4776 .expect("execution failed");
4777 assert!(result.ok());
4778 assert_eq!(result.out, "item: a\nitem: b\nitem: c\n");
4779 }
4780
4781 #[tokio::test]
4782 async fn test_for_command_subst_no_blank_lines() {
4783 let kernel = Kernel::transient().expect("kernel");
4784 let result = kernel
4785 .execute(r#"for N in $(seq 1 3); do echo "n=${N}"; done"#)
4786 .await
4787 .expect("execution failed");
4788 assert!(result.ok());
4789 assert_eq!(result.out, "n=1\nn=2\nn=3\n");
4790 }
4791
4792}