1use std::collections::HashMap;
26use std::path::PathBuf;
27use std::sync::Arc;
28
29use anyhow::{Context, Result};
30use tokio::sync::RwLock;
31
32use crate::ast::{Arg, Expr, Stmt, StringPart, ToolDef, Value, BinaryOp};
33use crate::backend::{BackendError, KernelBackend};
34use kaish_glob::glob_match;
35use crate::interpreter::{apply_output_format, eval_expr, expand_tilde, json_to_value, value_to_string, ControlFlow, ExecResult, Scope};
36use crate::parser::parse;
37use crate::scheduler::{drain_to_stream, BoundedStream, JobManager, PipelineRunner, DEFAULT_STREAM_MAX_SIZE};
38use crate::tools::{extract_output_format, register_builtins, resolve_in_path, ExecContext, ToolArgs, ToolRegistry};
39use crate::validator::{Severity, Validator};
40use crate::vfs::{JobFs, LocalFs, MemoryFs, VfsRouter};
41
42#[derive(Debug, Clone)]
49pub enum VfsMountMode {
50 Passthrough,
60
61 Sandboxed {
74 root: Option<PathBuf>,
77 },
78
79 NoLocal,
90}
91
92impl Default for VfsMountMode {
93 fn default() -> Self {
94 VfsMountMode::Sandboxed { root: None }
95 }
96}
97
98#[derive(Debug, Clone)]
100pub struct KernelConfig {
101 pub name: String,
103
104 pub vfs_mode: VfsMountMode,
106
107 pub cwd: PathBuf,
109
110 pub skip_validation: bool,
116}
117
118fn default_sandbox_root() -> PathBuf {
120 std::env::var("HOME")
121 .map(PathBuf::from)
122 .unwrap_or_else(|_| PathBuf::from("/"))
123}
124
125impl Default for KernelConfig {
126 fn default() -> Self {
127 let home = default_sandbox_root();
128 Self {
129 name: "default".to_string(),
130 vfs_mode: VfsMountMode::Sandboxed { root: None },
131 cwd: home,
132 skip_validation: false,
133 }
134 }
135}
136
137impl KernelConfig {
138 pub fn transient() -> Self {
140 let home = default_sandbox_root();
141 Self {
142 name: "transient".to_string(),
143 vfs_mode: VfsMountMode::Sandboxed { root: None },
144 cwd: home,
145 skip_validation: false,
146 }
147 }
148
149 pub fn named(name: &str) -> Self {
151 let home = default_sandbox_root();
152 Self {
153 name: name.to_string(),
154 vfs_mode: VfsMountMode::Sandboxed { root: None },
155 cwd: home,
156 skip_validation: false,
157 }
158 }
159
160 pub fn repl() -> Self {
165 let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("/"));
166 Self {
167 name: "repl".to_string(),
168 vfs_mode: VfsMountMode::Passthrough,
169 cwd,
170 skip_validation: false,
171 }
172 }
173
174 pub fn mcp() -> Self {
179 let home = default_sandbox_root();
180 Self {
181 name: "mcp".to_string(),
182 vfs_mode: VfsMountMode::Sandboxed { root: None },
183 cwd: home,
184 skip_validation: false,
185 }
186 }
187
188 pub fn mcp_with_root(root: PathBuf) -> Self {
192 Self {
193 name: "mcp".to_string(),
194 vfs_mode: VfsMountMode::Sandboxed { root: Some(root.clone()) },
195 cwd: root,
196 skip_validation: false,
197 }
198 }
199
200 pub fn isolated() -> Self {
204 Self {
205 name: "isolated".to_string(),
206 vfs_mode: VfsMountMode::NoLocal,
207 cwd: PathBuf::from("/"),
208 skip_validation: false,
209 }
210 }
211
212 pub fn with_vfs_mode(mut self, mode: VfsMountMode) -> Self {
214 self.vfs_mode = mode;
215 self
216 }
217
218 pub fn with_cwd(mut self, cwd: PathBuf) -> Self {
220 self.cwd = cwd;
221 self
222 }
223
224 pub fn with_skip_validation(mut self, skip: bool) -> Self {
226 self.skip_validation = skip;
227 self
228 }
229}
230
231pub struct Kernel {
236 name: String,
238 scope: RwLock<Scope>,
240 tools: Arc<ToolRegistry>,
242 user_tools: RwLock<HashMap<String, ToolDef>>,
244 vfs: Arc<VfsRouter>,
246 jobs: Arc<JobManager>,
248 runner: PipelineRunner,
250 exec_ctx: RwLock<ExecContext>,
252 skip_validation: bool,
254}
255
256impl Kernel {
257 pub fn new(config: KernelConfig) -> Result<Self> {
259 let mut vfs = Self::setup_vfs(&config);
260 let jobs = Arc::new(JobManager::new());
261
262 vfs.mount("/v/jobs", JobFs::new(jobs.clone()));
264
265 let vfs = Arc::new(vfs);
266
267 let mut tools = ToolRegistry::new();
269 register_builtins(&mut tools);
270 let tools = Arc::new(tools);
271
272 let runner = PipelineRunner::new(tools.clone());
274
275 let scope = Scope::new();
276 let cwd = config.cwd;
277
278 let mut exec_ctx = ExecContext::with_vfs_and_tools(vfs.clone(), tools.clone());
280 exec_ctx.set_cwd(cwd);
281 exec_ctx.set_job_manager(jobs.clone());
282 exec_ctx.set_tool_schemas(tools.schemas());
283
284 Ok(Self {
285 name: config.name,
286 scope: RwLock::new(scope),
287 tools,
288 user_tools: RwLock::new(HashMap::new()),
289 vfs,
290 jobs,
291 runner,
292 exec_ctx: RwLock::new(exec_ctx),
293 skip_validation: config.skip_validation,
294 })
295 }
296
297 fn setup_vfs(config: &KernelConfig) -> VfsRouter {
299 let mut vfs = VfsRouter::new();
300
301 match &config.vfs_mode {
302 VfsMountMode::Passthrough => {
303 vfs.mount("/", LocalFs::new(PathBuf::from("/")));
305 vfs.mount("/v", MemoryFs::new());
307 vfs.mount("/scratch", MemoryFs::new());
308 }
309 VfsMountMode::Sandboxed { root } => {
310 vfs.mount("/", MemoryFs::new());
312 vfs.mount("/v", MemoryFs::new());
313 vfs.mount("/scratch", MemoryFs::new());
314
315 vfs.mount("/tmp", LocalFs::new(PathBuf::from("/tmp")));
317
318 let local_root = root.clone().unwrap_or_else(|| {
320 std::env::var("HOME")
321 .map(PathBuf::from)
322 .unwrap_or_else(|_| PathBuf::from("/"))
323 });
324
325 let mount_point = local_root.to_string_lossy().to_string();
329 vfs.mount(&mount_point, LocalFs::new(local_root));
330 }
331 VfsMountMode::NoLocal => {
332 vfs.mount("/", MemoryFs::new());
334 vfs.mount("/tmp", MemoryFs::new());
335 vfs.mount("/v", MemoryFs::new());
336 vfs.mount("/scratch", MemoryFs::new());
337 }
338 }
339
340 vfs
341 }
342
343 pub fn transient() -> Result<Self> {
345 Self::new(KernelConfig::transient())
346 }
347
348 pub fn with_backend(backend: Arc<dyn KernelBackend>, config: KernelConfig) -> Result<Self> {
357 let mut vfs = Self::setup_vfs(&config);
359 let jobs = Arc::new(JobManager::new());
360
361 vfs.mount("/v/jobs", JobFs::new(jobs.clone()));
363
364 let vfs = Arc::new(vfs);
365
366 let mut tools = ToolRegistry::new();
368 register_builtins(&mut tools);
369 let tools = Arc::new(tools);
370
371 let runner = PipelineRunner::new(tools.clone());
373
374 let scope = Scope::new();
375 let cwd = config.cwd;
376
377 let mut exec_ctx = ExecContext::with_backend(backend);
379 exec_ctx.set_cwd(cwd);
380 exec_ctx.set_job_manager(jobs.clone());
381 exec_ctx.set_tool_schemas(tools.schemas());
382 exec_ctx.set_tools(tools.clone());
383
384 Ok(Self {
385 name: config.name,
386 scope: RwLock::new(scope),
387 tools,
388 user_tools: RwLock::new(HashMap::new()),
389 vfs,
390 jobs,
391 runner,
392 exec_ctx: RwLock::new(exec_ctx),
393 skip_validation: config.skip_validation,
394 })
395 }
396
397 pub fn with_backend_and_virtual_paths(
417 backend: Arc<dyn KernelBackend>,
418 config: KernelConfig,
419 ) -> Result<Self> {
420 use crate::backend::VirtualOverlayBackend;
421
422 let mut vfs = VfsRouter::new();
424 let jobs = Arc::new(JobManager::new());
425
426 vfs.mount("/v/jobs", JobFs::new(jobs.clone()));
428 vfs.mount("/v/blobs", MemoryFs::new());
430 vfs.mount("/v/scratch", MemoryFs::new());
432
433 let vfs = Arc::new(vfs);
434
435 let overlay: Arc<dyn KernelBackend> = Arc::new(VirtualOverlayBackend::new(backend, vfs.clone()));
437
438 let mut tools = ToolRegistry::new();
440 register_builtins(&mut tools);
441 let tools = Arc::new(tools);
442
443 let runner = PipelineRunner::new(tools.clone());
445
446 let scope = Scope::new();
447 let cwd = config.cwd;
448
449 let mut exec_ctx = ExecContext::with_backend(overlay);
451 exec_ctx.set_cwd(cwd);
452 exec_ctx.set_job_manager(jobs.clone());
453 exec_ctx.set_tool_schemas(tools.schemas());
454 exec_ctx.set_tools(tools.clone());
455
456 Ok(Self {
457 name: config.name,
458 scope: RwLock::new(scope),
459 tools,
460 user_tools: RwLock::new(HashMap::new()),
461 vfs,
462 jobs,
463 runner,
464 exec_ctx: RwLock::new(exec_ctx),
465 skip_validation: config.skip_validation,
466 })
467 }
468
469 pub fn name(&self) -> &str {
471 &self.name
472 }
473
474 pub async fn execute(&self, input: &str) -> Result<ExecResult> {
478
479 let program = parse(input).map_err(|errors| {
480 let msg = errors
481 .iter()
482 .map(|e| e.to_string())
483 .collect::<Vec<_>>()
484 .join("; ");
485 anyhow::anyhow!("parse error: {}", msg)
486 })?;
487
488 if !self.skip_validation {
490 let user_tools = self.user_tools.read().await;
491 let validator = Validator::new(&self.tools, &user_tools);
492 let issues = validator.validate(&program);
493
494 let errors: Vec<_> = issues
496 .iter()
497 .filter(|i| i.severity == Severity::Error)
498 .collect();
499
500 if !errors.is_empty() {
501 let error_msg = errors
502 .iter()
503 .map(|e| e.format(input))
504 .collect::<Vec<_>>()
505 .join("\n");
506 return Err(anyhow::anyhow!("validation failed:\n{}", error_msg));
507 }
508
509 for warning in issues.iter().filter(|i| i.severity == Severity::Warning) {
511 tracing::trace!("validation: {}", warning.format(input));
512 }
513 }
514
515 let mut result = ExecResult::success("");
516
517 for stmt in program.statements {
518 if matches!(stmt, Stmt::Empty) {
519 continue;
520 }
521 let flow = self.execute_stmt_flow(&stmt).await?;
522 match flow {
523 ControlFlow::Normal(r) => accumulate_result(&mut result, &r),
524 ControlFlow::Exit { code } => {
525 let exit_result = ExecResult::success(code.to_string());
527 return Ok(exit_result);
528 }
529 ControlFlow::Return { value } => {
530 result = value;
532 }
533 ControlFlow::Break { result: r, .. } | ControlFlow::Continue { result: r, .. } => {
534 result = r;
536 }
537 }
538 }
539
540 Ok(result)
541 }
542
543 fn execute_stmt_flow<'a>(
545 &'a self,
546 stmt: &'a Stmt,
547 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<ControlFlow>> + 'a>> {
548 Box::pin(async move {
549 match stmt {
550 Stmt::Assignment(assign) => {
551 let value = self.eval_expr_async(&assign.value).await
553 .context("failed to evaluate assignment")?;
554 let mut scope = self.scope.write().await;
555 if assign.local {
556 scope.set(&assign.name, value.clone());
558 } else {
559 scope.set_global(&assign.name, value.clone());
561 }
562 drop(scope);
563
564 Ok(ControlFlow::ok(ExecResult::success("")))
566 }
567 Stmt::Command(cmd) => {
568 let result = self.execute_command(&cmd.name, &cmd.args).await?;
569 self.update_last_result(&result).await;
570
571 if !result.ok() {
573 let scope = self.scope.read().await;
574 if scope.error_exit_enabled() {
575 return Ok(ControlFlow::exit_code(result.code));
576 }
577 }
578
579 Ok(ControlFlow::ok(result))
580 }
581 Stmt::Pipeline(pipeline) => {
582 let result = self.execute_pipeline(pipeline).await?;
583 self.update_last_result(&result).await;
584
585 if !result.ok() {
587 let scope = self.scope.read().await;
588 if scope.error_exit_enabled() {
589 return Ok(ControlFlow::exit_code(result.code));
590 }
591 }
592
593 Ok(ControlFlow::ok(result))
594 }
595 Stmt::If(if_stmt) => {
596 let cond_value = self.eval_expr_async(&if_stmt.condition).await?;
598
599 let branch = if is_truthy(&cond_value) {
600 &if_stmt.then_branch
601 } else {
602 if_stmt.else_branch.as_deref().unwrap_or(&[])
603 };
604
605 let mut flow = ControlFlow::ok(ExecResult::success(""));
606 for stmt in branch {
607 flow = self.execute_stmt_flow(stmt).await?;
608 if !flow.is_normal() {
609 return Ok(flow);
610 }
611 }
612 Ok(flow)
613 }
614 Stmt::For(for_loop) => {
615 let mut items: Vec<Value> = Vec::new();
618 for item_expr in &for_loop.items {
619 let item = self.eval_expr_async(item_expr).await?;
620 match &item {
622 Value::Json(serde_json::Value::Array(arr)) => {
624 for elem in arr {
625 items.push(json_to_value(elem.clone()));
626 }
627 }
628 Value::String(_) => {
631 items.push(item);
632 }
633 _ => items.push(item),
635 }
636 }
637
638 let mut result = ExecResult::success("");
639 {
640 let mut scope = self.scope.write().await;
641 scope.push_frame();
642 }
643
644 'outer: for item in items {
645 {
646 let mut scope = self.scope.write().await;
647 scope.set(&for_loop.variable, item);
648 }
649 for stmt in &for_loop.body {
650 let mut flow = self.execute_stmt_flow(stmt).await?;
651 match &mut flow {
652 ControlFlow::Normal(r) => accumulate_result(&mut result, r),
653 ControlFlow::Break { .. } => {
654 if flow.decrement_level() {
655 break 'outer;
657 }
658 let mut scope = self.scope.write().await;
660 scope.pop_frame();
661 return Ok(flow);
662 }
663 ControlFlow::Continue { .. } => {
664 if flow.decrement_level() {
665 continue 'outer;
667 }
668 let mut scope = self.scope.write().await;
670 scope.pop_frame();
671 return Ok(flow);
672 }
673 ControlFlow::Return { .. } | ControlFlow::Exit { .. } => {
674 let mut scope = self.scope.write().await;
675 scope.pop_frame();
676 return Ok(flow);
677 }
678 }
679 }
680 }
681
682 {
683 let mut scope = self.scope.write().await;
684 scope.pop_frame();
685 }
686 Ok(ControlFlow::ok(result))
687 }
688 Stmt::While(while_loop) => {
689 let mut result = ExecResult::success("");
690
691 'outer: loop {
692 let cond_value = self.eval_expr_async(&while_loop.condition).await?;
694
695 if !is_truthy(&cond_value) {
696 break;
697 }
698
699 for stmt in &while_loop.body {
701 let mut flow = self.execute_stmt_flow(stmt).await?;
702 match &mut flow {
703 ControlFlow::Normal(r) => accumulate_result(&mut result, r),
704 ControlFlow::Break { .. } => {
705 if flow.decrement_level() {
706 break 'outer;
708 }
709 return Ok(flow);
711 }
712 ControlFlow::Continue { .. } => {
713 if flow.decrement_level() {
714 continue 'outer;
716 }
717 return Ok(flow);
719 }
720 ControlFlow::Return { .. } | ControlFlow::Exit { .. } => {
721 return Ok(flow);
722 }
723 }
724 }
725 }
726
727 Ok(ControlFlow::ok(result))
728 }
729 Stmt::Case(case_stmt) => {
730 let match_value = {
732 let mut scope = self.scope.write().await;
733 let value = eval_expr(&case_stmt.expr, &mut scope)?;
734 value_to_string(&value)
735 };
736
737 for branch in &case_stmt.branches {
739 let matched = branch.patterns.iter().any(|pattern| {
740 glob_match(pattern, &match_value)
741 });
742
743 if matched {
744 let mut result = ControlFlow::ok(ExecResult::success(""));
746 for stmt in &branch.body {
747 result = self.execute_stmt_flow(stmt).await?;
748 if !result.is_normal() {
749 return Ok(result);
750 }
751 }
752 return Ok(result);
753 }
754 }
755
756 Ok(ControlFlow::ok(ExecResult::success("")))
758 }
759 Stmt::Break(levels) => {
760 Ok(ControlFlow::break_n(levels.unwrap_or(1)))
761 }
762 Stmt::Continue(levels) => {
763 Ok(ControlFlow::continue_n(levels.unwrap_or(1)))
764 }
765 Stmt::Return(expr) => {
766 let result = if let Some(e) = expr {
769 let mut scope = self.scope.write().await;
770 let val = eval_expr(e, &mut scope)?;
771 let code = match val {
773 Value::Int(n) => n,
774 Value::Bool(b) => if b { 0 } else { 1 },
775 _ => 0,
776 };
777 ExecResult {
778 code,
779 out: String::new(),
780 err: String::new(),
781 data: None,
782 output: None,
783 }
784 } else {
785 ExecResult::success("")
786 };
787 Ok(ControlFlow::return_value(result))
788 }
789 Stmt::Exit(expr) => {
790 let code = if let Some(e) = expr {
791 let mut scope = self.scope.write().await;
792 let val = eval_expr(e, &mut scope)?;
793 match val {
794 Value::Int(n) => n,
795 _ => 0,
796 }
797 } else {
798 0
799 };
800 Ok(ControlFlow::exit_code(code))
801 }
802 Stmt::ToolDef(tool_def) => {
803 let mut user_tools = self.user_tools.write().await;
804 user_tools.insert(tool_def.name.clone(), tool_def.clone());
805 Ok(ControlFlow::ok(ExecResult::success("")))
806 }
807 Stmt::AndChain { left, right } => {
808 let left_flow = self.execute_stmt_flow(left).await?;
810 match left_flow {
811 ControlFlow::Normal(left_result) => {
812 self.update_last_result(&left_result).await;
813 if left_result.ok() {
814 let right_flow = self.execute_stmt_flow(right).await?;
815 match right_flow {
816 ControlFlow::Normal(right_result) => {
817 self.update_last_result(&right_result).await;
818 let mut combined = left_result;
820 accumulate_result(&mut combined, &right_result);
821 Ok(ControlFlow::ok(combined))
822 }
823 other => Ok(other), }
825 } else {
826 Ok(ControlFlow::ok(left_result))
827 }
828 }
829 _ => Ok(left_flow), }
831 }
832 Stmt::OrChain { left, right } => {
833 let left_flow = self.execute_stmt_flow(left).await?;
835 match left_flow {
836 ControlFlow::Normal(left_result) => {
837 self.update_last_result(&left_result).await;
838 if !left_result.ok() {
839 let right_flow = self.execute_stmt_flow(right).await?;
840 match right_flow {
841 ControlFlow::Normal(right_result) => {
842 self.update_last_result(&right_result).await;
843 let mut combined = left_result;
845 accumulate_result(&mut combined, &right_result);
846 Ok(ControlFlow::ok(combined))
847 }
848 other => Ok(other), }
850 } else {
851 Ok(ControlFlow::ok(left_result))
852 }
853 }
854 _ => Ok(left_flow), }
856 }
857 Stmt::Test(test_expr) => {
858 let expr = crate::ast::Expr::Test(Box::new(test_expr.clone()));
860 let mut scope = self.scope.write().await;
861 let value = eval_expr(&expr, &mut scope)?;
862 drop(scope);
863 let is_true = match value {
864 crate::ast::Value::Bool(b) => b,
865 _ => false,
866 };
867 if is_true {
868 Ok(ControlFlow::ok(ExecResult::success("")))
869 } else {
870 Ok(ControlFlow::ok(ExecResult::failure(1, "")))
871 }
872 }
873 Stmt::Empty => Ok(ControlFlow::ok(ExecResult::success(""))),
874 }
875 })
876 }
877
878 async fn execute_pipeline(&self, pipeline: &crate::ast::Pipeline) -> Result<ExecResult> {
880 if pipeline.commands.is_empty() {
881 return Ok(ExecResult::success(""));
882 }
883
884 if pipeline.background {
886 return self.execute_background(pipeline).await;
887 }
888
889 if pipeline.commands.len() == 1 && pipeline.commands[0].redirects.is_empty() {
891 let cmd = &pipeline.commands[0];
892 return self.execute_command(&cmd.name, &cmd.args).await;
893 }
894
895 let mut ctx = self.exec_ctx.write().await;
897 {
898 let scope = self.scope.read().await;
899 ctx.scope = scope.clone();
900 }
901
902 let result = self.runner.run(&pipeline.commands, &mut ctx).await;
903
904 {
906 let mut scope = self.scope.write().await;
907 *scope = ctx.scope.clone();
908 }
909
910 Ok(result)
911 }
912
913 async fn execute_background(&self, pipeline: &crate::ast::Pipeline) -> Result<ExecResult> {
921 use tokio::sync::oneshot;
922
923 let command_str = self.format_pipeline(pipeline);
925
926 let stdout = Arc::new(BoundedStream::default_size());
928 let stderr = Arc::new(BoundedStream::default_size());
929
930 let (tx, rx) = oneshot::channel();
932
933 let job_id = self.jobs.register_with_streams(
935 command_str.clone(),
936 rx,
937 stdout.clone(),
938 stderr.clone(),
939 ).await;
940
941 let runner = self.runner.clone();
943 let commands = pipeline.commands.clone();
944 let backend = {
945 let ctx = self.exec_ctx.read().await;
946 ctx.backend.clone()
947 };
948 let scope = {
949 let scope = self.scope.read().await;
950 scope.clone()
951 };
952 let cwd = {
953 let ctx = self.exec_ctx.read().await;
954 ctx.cwd.clone()
955 };
956 let tools = self.tools.clone();
957 let tool_schemas = self.tools.schemas();
958
959 tokio::spawn(async move {
961 let mut bg_ctx = ExecContext::with_backend(backend);
964 bg_ctx.scope = scope;
965 bg_ctx.cwd = cwd;
966 bg_ctx.set_tools(tools);
967 bg_ctx.set_tool_schemas(tool_schemas);
968
969 let result = runner.run(&commands, &mut bg_ctx).await;
971
972 if !result.out.is_empty() {
974 stdout.write(result.out.as_bytes()).await;
975 }
976 if !result.err.is_empty() {
977 stderr.write(result.err.as_bytes()).await;
978 }
979
980 stdout.close().await;
982 stderr.close().await;
983
984 let _ = tx.send(result);
986 });
987
988 Ok(ExecResult::success(format!("[{}]", job_id)))
989 }
990
991 fn format_pipeline(&self, pipeline: &crate::ast::Pipeline) -> String {
993 pipeline.commands
994 .iter()
995 .map(|cmd| {
996 let mut parts = vec![cmd.name.clone()];
997 for arg in &cmd.args {
998 match arg {
999 Arg::Positional(expr) => {
1000 parts.push(self.format_expr(expr));
1001 }
1002 Arg::Named { key, value } => {
1003 parts.push(format!("{}={}", key, self.format_expr(value)));
1004 }
1005 Arg::ShortFlag(name) => {
1006 parts.push(format!("-{}", name));
1007 }
1008 Arg::LongFlag(name) => {
1009 parts.push(format!("--{}", name));
1010 }
1011 Arg::DoubleDash => {
1012 parts.push("--".to_string());
1013 }
1014 }
1015 }
1016 parts.join(" ")
1017 })
1018 .collect::<Vec<_>>()
1019 .join(" | ")
1020 }
1021
1022 fn format_expr(&self, expr: &Expr) -> String {
1024 match expr {
1025 Expr::Literal(Value::String(s)) => {
1026 if s.contains(' ') || s.contains('"') {
1027 format!("'{}'", s.replace('\'', "\\'"))
1028 } else {
1029 s.clone()
1030 }
1031 }
1032 Expr::Literal(Value::Int(i)) => i.to_string(),
1033 Expr::Literal(Value::Float(f)) => f.to_string(),
1034 Expr::Literal(Value::Bool(b)) => b.to_string(),
1035 Expr::Literal(Value::Null) => "null".to_string(),
1036 Expr::VarRef(path) => {
1037 let name = path.segments.iter()
1038 .map(|seg| match seg {
1039 crate::ast::VarSegment::Field(f) => f.clone(),
1040 })
1041 .collect::<Vec<_>>()
1042 .join(".");
1043 format!("${{{}}}", name)
1044 }
1045 Expr::Interpolated(_) => "\"...\"".to_string(),
1046 _ => "...".to_string(),
1047 }
1048 }
1049
1050 async fn execute_command(&self, name: &str, args: &[Arg]) -> Result<ExecResult> {
1052 match name {
1054 "true" => return Ok(ExecResult::success("")),
1055 "false" => return Ok(ExecResult::failure(1, "")),
1056 "source" | "." => return self.execute_source(args).await,
1057 _ => {}
1058 }
1059
1060 {
1062 let user_tools = self.user_tools.read().await;
1063 if let Some(tool_def) = user_tools.get(name) {
1064 let tool_def = tool_def.clone();
1065 drop(user_tools);
1066 return self.execute_user_tool(tool_def, args).await;
1067 }
1068 }
1069
1070 let tool = match self.tools.get(name) {
1072 Some(t) => t,
1073 None => {
1074 if let Some(result) = self.try_execute_script(name, args).await? {
1076 return Ok(result);
1077 }
1078 if let Some(result) = self.try_execute_external(name, args).await? {
1080 return Ok(result);
1081 }
1082
1083 let tool_args = self.build_args_async(args).await?;
1085 let mut ctx = self.exec_ctx.write().await;
1086 {
1087 let scope = self.scope.read().await;
1088 ctx.scope = scope.clone();
1089 }
1090 let backend = ctx.backend.clone();
1091 match backend.call_tool(name, tool_args, &mut ctx).await {
1092 Ok(tool_result) => {
1093 let mut scope = self.scope.write().await;
1094 *scope = ctx.scope.clone();
1095 let mut exec = ExecResult::from_output(
1096 tool_result.code as i64, tool_result.stdout, tool_result.stderr,
1097 );
1098 exec.output = tool_result.output;
1099 return Ok(exec);
1100 }
1101 Err(BackendError::ToolNotFound(_)) => {
1102 }
1104 Err(e) => {
1105 return Ok(ExecResult::failure(1, e.to_string()));
1106 }
1107 }
1108
1109 return Ok(ExecResult::failure(127, format!("command not found: {}", name)));
1110 }
1111 };
1112
1113 let mut tool_args = self.build_args_async(args).await?;
1115 let output_format = extract_output_format(&mut tool_args, Some(&tool.schema()));
1116
1117 let mut ctx = self.exec_ctx.write().await;
1119 {
1120 let scope = self.scope.read().await;
1121 ctx.scope = scope.clone();
1122 }
1123
1124 let result = tool.execute(tool_args, &mut ctx).await;
1125
1126 {
1128 let mut scope = self.scope.write().await;
1129 *scope = ctx.scope.clone();
1130 }
1131
1132 let result = match output_format {
1133 Some(format) => apply_output_format(result, format),
1134 None => result,
1135 };
1136
1137 Ok(result)
1138 }
1139
1140 async fn build_args_async(&self, args: &[Arg]) -> Result<ToolArgs> {
1144 let mut tool_args = ToolArgs::new();
1145
1146 for arg in args {
1147 match arg {
1148 Arg::Positional(expr) => {
1149 let value = self.eval_expr_async(expr).await?;
1150 let value = apply_tilde_expansion(value);
1152 tool_args.positional.push(value);
1153 }
1154 Arg::Named { key, value } => {
1155 let val = self.eval_expr_async(value).await?;
1156 let val = apply_tilde_expansion(val);
1158 tool_args.named.insert(key.clone(), val);
1159 }
1160 Arg::ShortFlag(name) => {
1161 for c in name.chars() {
1162 tool_args.flags.insert(c.to_string());
1163 }
1164 }
1165 Arg::LongFlag(name) => {
1166 tool_args.flags.insert(name.clone());
1167 }
1168 Arg::DoubleDash => {
1169 }
1172 }
1173 }
1174
1175 Ok(tool_args)
1176 }
1177
1178 async fn build_args_flat(&self, args: &[Arg]) -> Result<Vec<String>> {
1188 let mut argv = Vec::new();
1189 for arg in args {
1190 match arg {
1191 Arg::Positional(expr) => {
1192 let value = self.eval_expr_async(expr).await?;
1193 let value = apply_tilde_expansion(value);
1194 argv.push(value_to_string(&value));
1195 }
1196 Arg::Named { key, value } => {
1197 let val = self.eval_expr_async(value).await?;
1198 let val = apply_tilde_expansion(val);
1199 argv.push(format!("{}={}", key, value_to_string(&val)));
1200 }
1201 Arg::ShortFlag(name) => {
1202 argv.push(format!("-{}", name));
1204 }
1205 Arg::LongFlag(name) => {
1206 argv.push(format!("--{}", name));
1208 }
1209 Arg::DoubleDash => {
1210 argv.push("--".to_string());
1212 }
1213 }
1214 }
1215 Ok(argv)
1216 }
1217
1218 fn eval_expr_async<'a>(&'a self, expr: &'a Expr) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Value>> + 'a>> {
1223 Box::pin(async move {
1224 match expr {
1225 Expr::Literal(value) => Ok(value.clone()),
1226 Expr::VarRef(path) => {
1227 let scope = self.scope.read().await;
1228 scope.resolve_path(path)
1229 .ok_or_else(|| anyhow::anyhow!("undefined variable"))
1230 }
1231 Expr::Interpolated(parts) => {
1232 let mut result = String::new();
1233 for part in parts {
1234 result.push_str(&self.eval_string_part_async(part).await?);
1235 }
1236 Ok(Value::String(result))
1237 }
1238 Expr::BinaryOp { left, op, right } => {
1239 match op {
1240 BinaryOp::And => {
1241 let left_val = self.eval_expr_async(left).await?;
1242 if !is_truthy(&left_val) {
1243 return Ok(left_val);
1244 }
1245 self.eval_expr_async(right).await
1246 }
1247 BinaryOp::Or => {
1248 let left_val = self.eval_expr_async(left).await?;
1249 if is_truthy(&left_val) {
1250 return Ok(left_val);
1251 }
1252 self.eval_expr_async(right).await
1253 }
1254 _ => {
1255 let mut scope = self.scope.write().await;
1257 eval_expr(expr, &mut scope).map_err(|e| anyhow::anyhow!("{}", e))
1258 }
1259 }
1260 }
1261 Expr::CommandSubst(pipeline) => {
1262 let result = self.execute_pipeline(pipeline).await?;
1264 self.update_last_result(&result).await;
1265 if let Some(data) = &result.data {
1267 Ok(data.clone())
1268 } else {
1269 Ok(Value::String(result.out.trim_end().to_string()))
1271 }
1272 }
1273 Expr::Test(test_expr) => {
1274 let expr = Expr::Test(test_expr.clone());
1276 let mut scope = self.scope.write().await;
1277 eval_expr(&expr, &mut scope).map_err(|e| anyhow::anyhow!("{}", e))
1278 }
1279 Expr::Positional(n) => {
1280 let scope = self.scope.read().await;
1281 match scope.get_positional(*n) {
1282 Some(s) => Ok(Value::String(s.to_string())),
1283 None => Ok(Value::String(String::new())),
1284 }
1285 }
1286 Expr::AllArgs => {
1287 let scope = self.scope.read().await;
1288 Ok(Value::String(scope.all_args().join(" ")))
1289 }
1290 Expr::ArgCount => {
1291 let scope = self.scope.read().await;
1292 Ok(Value::Int(scope.arg_count() as i64))
1293 }
1294 Expr::VarLength(name) => {
1295 let scope = self.scope.read().await;
1296 match scope.get(name) {
1297 Some(value) => Ok(Value::Int(value_to_string(value).len() as i64)),
1298 None => Ok(Value::Int(0)),
1299 }
1300 }
1301 Expr::VarWithDefault { name, default } => {
1302 let scope = self.scope.read().await;
1303 let use_default = match scope.get(name) {
1304 Some(value) => value_to_string(value).is_empty(),
1305 None => true,
1306 };
1307 drop(scope); if use_default {
1309 self.eval_string_parts_async(default).await.map(Value::String)
1311 } else {
1312 let scope = self.scope.read().await;
1313 scope.get(name).cloned().ok_or_else(|| anyhow::anyhow!("variable '{}' not found", name))
1314 }
1315 }
1316 Expr::Arithmetic(expr_str) => {
1317 let scope = self.scope.read().await;
1318 crate::arithmetic::eval_arithmetic(expr_str, &scope)
1319 .map(Value::Int)
1320 .map_err(|e| anyhow::anyhow!("arithmetic error: {}", e))
1321 }
1322 Expr::Command(cmd) => {
1323 let result = self.execute_command(&cmd.name, &cmd.args).await?;
1325 Ok(Value::Bool(result.code == 0))
1326 }
1327 Expr::LastExitCode => {
1328 let scope = self.scope.read().await;
1329 Ok(Value::Int(scope.last_result().code))
1330 }
1331 Expr::CurrentPid => {
1332 let scope = self.scope.read().await;
1333 Ok(Value::Int(scope.pid() as i64))
1334 }
1335 }
1336 })
1337 }
1338
1339 fn eval_string_parts_async<'a>(&'a self, parts: &'a [StringPart]) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<String>> + 'a>> {
1341 Box::pin(async move {
1342 let mut result = String::new();
1343 for part in parts {
1344 result.push_str(&self.eval_string_part_async(part).await?);
1345 }
1346 Ok(result)
1347 })
1348 }
1349
1350 fn eval_string_part_async<'a>(&'a self, part: &'a StringPart) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<String>> + 'a>> {
1352 Box::pin(async move {
1353 match part {
1354 StringPart::Literal(s) => Ok(s.clone()),
1355 StringPart::Var(path) => {
1356 let scope = self.scope.read().await;
1357 match scope.resolve_path(path) {
1358 Some(value) => Ok(value_to_string(&value)),
1359 None => Ok(String::new()), }
1361 }
1362 StringPart::VarWithDefault { name, default } => {
1363 let scope = self.scope.read().await;
1364 let use_default = match scope.get(name) {
1365 Some(value) => value_to_string(value).is_empty(),
1366 None => true,
1367 };
1368 drop(scope); if use_default {
1370 self.eval_string_parts_async(default).await
1372 } else {
1373 let scope = self.scope.read().await;
1374 Ok(value_to_string(scope.get(name).ok_or_else(|| anyhow::anyhow!("variable '{}' not found", name))?))
1375 }
1376 }
1377 StringPart::VarLength(name) => {
1378 let scope = self.scope.read().await;
1379 match scope.get(name) {
1380 Some(value) => Ok(value_to_string(value).len().to_string()),
1381 None => Ok("0".to_string()),
1382 }
1383 }
1384 StringPart::Positional(n) => {
1385 let scope = self.scope.read().await;
1386 match scope.get_positional(*n) {
1387 Some(s) => Ok(s.to_string()),
1388 None => Ok(String::new()),
1389 }
1390 }
1391 StringPart::AllArgs => {
1392 let scope = self.scope.read().await;
1393 Ok(scope.all_args().join(" "))
1394 }
1395 StringPart::ArgCount => {
1396 let scope = self.scope.read().await;
1397 Ok(scope.arg_count().to_string())
1398 }
1399 StringPart::Arithmetic(expr) => {
1400 let scope = self.scope.read().await;
1401 match crate::arithmetic::eval_arithmetic(expr, &scope) {
1402 Ok(value) => Ok(value.to_string()),
1403 Err(_) => Ok(String::new()),
1404 }
1405 }
1406 StringPart::CommandSubst(pipeline) => {
1407 let result = self.execute_pipeline(pipeline).await?;
1409 Ok(result.out.trim_end_matches('\n').to_string())
1410 }
1411 StringPart::LastExitCode => {
1412 let scope = self.scope.read().await;
1413 Ok(scope.last_result().code.to_string())
1414 }
1415 StringPart::CurrentPid => {
1416 let scope = self.scope.read().await;
1417 Ok(scope.pid().to_string())
1418 }
1419 }
1420 })
1421 }
1422
1423 async fn update_last_result(&self, result: &ExecResult) {
1425 let mut scope = self.scope.write().await;
1426 scope.set_last_result(result.clone());
1427 }
1428
1429 async fn execute_user_tool(&self, def: ToolDef, args: &[Arg]) -> Result<ExecResult> {
1435 let tool_args = self.build_args_async(args).await?;
1437
1438 {
1440 let mut scope = self.scope.write().await;
1441 scope.push_frame();
1442 }
1443
1444 let saved_positional = {
1446 let mut scope = self.scope.write().await;
1447 let saved = scope.save_positional();
1448
1449 let positional_args: Vec<String> = tool_args.positional
1451 .iter()
1452 .map(value_to_string)
1453 .collect();
1454 scope.set_positional(&def.name, positional_args);
1455
1456 saved
1457 };
1458
1459 let mut accumulated_out = String::new();
1462 let mut accumulated_err = String::new();
1463 let mut last_code = 0i64;
1464 let mut last_data: Option<Value> = None;
1465
1466 for stmt in &def.body {
1467 match self.execute_stmt_flow(stmt).await {
1468 Ok(flow) => {
1469 match flow {
1470 ControlFlow::Normal(r) => {
1471 accumulated_out.push_str(&r.out);
1472 accumulated_err.push_str(&r.err);
1473 last_code = r.code;
1474 last_data = r.data;
1475 }
1476 ControlFlow::Return { value } => {
1477 accumulated_out.push_str(&value.out);
1479 accumulated_err.push_str(&value.err);
1480 last_code = value.code;
1481 last_data = value.data;
1482 break;
1483 }
1484 ControlFlow::Exit { code } => {
1485 let mut scope = self.scope.write().await;
1487 scope.pop_frame();
1488 scope.set_positional(saved_positional.0.clone(), saved_positional.1.clone());
1489 return Ok(ExecResult::failure(code, "exit"));
1490 }
1491 ControlFlow::Break { result: r, .. } | ControlFlow::Continue { result: r, .. } => {
1492 accumulated_out.push_str(&r.out);
1494 accumulated_err.push_str(&r.err);
1495 last_code = r.code;
1496 last_data = r.data;
1497 }
1498 }
1499 }
1500 Err(e) => {
1501 let mut scope = self.scope.write().await;
1503 scope.pop_frame();
1504 scope.set_positional(saved_positional.0.clone(), saved_positional.1.clone());
1505 return Err(e);
1506 }
1507 }
1508 }
1509
1510 let result = ExecResult {
1511 code: last_code,
1512 out: accumulated_out,
1513 err: accumulated_err,
1514 data: last_data,
1515 output: None,
1516 };
1517
1518 {
1520 let mut scope = self.scope.write().await;
1521 scope.pop_frame();
1522 scope.set_positional(saved_positional.0, saved_positional.1);
1523 }
1524
1525 Ok(result)
1527 }
1528
1529 async fn execute_source(&self, args: &[Arg]) -> Result<ExecResult> {
1534 let tool_args = self.build_args_async(args).await?;
1536 let path = match tool_args.positional.first() {
1537 Some(Value::String(s)) => s.clone(),
1538 Some(v) => value_to_string(v),
1539 None => {
1540 return Ok(ExecResult::failure(1, "source: missing filename"));
1541 }
1542 };
1543
1544 let full_path = {
1546 let ctx = self.exec_ctx.read().await;
1547 if path.starts_with('/') {
1548 std::path::PathBuf::from(&path)
1549 } else {
1550 ctx.cwd.join(&path)
1551 }
1552 };
1553
1554 let content = {
1556 let ctx = self.exec_ctx.read().await;
1557 match ctx.backend.read(&full_path, None).await {
1558 Ok(bytes) => {
1559 String::from_utf8(bytes).map_err(|e| {
1560 anyhow::anyhow!("source: {}: invalid UTF-8: {}", path, e)
1561 })?
1562 }
1563 Err(e) => {
1564 return Ok(ExecResult::failure(
1565 1,
1566 format!("source: {}: {}", path, e),
1567 ));
1568 }
1569 }
1570 };
1571
1572 let program = match crate::parser::parse(&content) {
1574 Ok(p) => p,
1575 Err(errors) => {
1576 let msg = errors
1577 .iter()
1578 .map(|e| format!("{}:{}: {}", path, e.span.start, e.message))
1579 .collect::<Vec<_>>()
1580 .join("\n");
1581 return Ok(ExecResult::failure(1, format!("source: {}", msg)));
1582 }
1583 };
1584
1585 let mut result = ExecResult::success("");
1587 for stmt in program.statements {
1588 if matches!(stmt, crate::ast::Stmt::Empty) {
1589 continue;
1590 }
1591
1592 match self.execute_stmt_flow(&stmt).await {
1593 Ok(flow) => {
1594 match flow {
1595 ControlFlow::Normal(r) => {
1596 result = r.clone();
1597 self.update_last_result(&r).await;
1598 }
1599 ControlFlow::Break { .. } | ControlFlow::Continue { .. } => {
1600 return Err(anyhow::anyhow!(
1602 "source: {}: unexpected break/continue outside loop",
1603 path
1604 ));
1605 }
1606 ControlFlow::Return { value } => {
1607 return Ok(value);
1609 }
1610 ControlFlow::Exit { code } => {
1611 return Ok(ExecResult::failure(code, "exit"));
1613 }
1614 }
1615 }
1616 Err(e) => {
1617 return Err(e.context(format!("source: {}", path)));
1618 }
1619 }
1620 }
1621
1622 Ok(result)
1623 }
1624
1625 async fn try_execute_script(&self, name: &str, args: &[Arg]) -> Result<Option<ExecResult>> {
1630 let path_value = {
1632 let scope = self.scope.read().await;
1633 scope
1634 .get("PATH")
1635 .map(value_to_string)
1636 .unwrap_or_else(|| "/bin".to_string())
1637 };
1638
1639 for dir in path_value.split(':') {
1641 if dir.is_empty() {
1642 continue;
1643 }
1644
1645 let script_path = PathBuf::from(dir).join(format!("{}.kai", name));
1647
1648 let exists = {
1650 let ctx = self.exec_ctx.read().await;
1651 ctx.backend.exists(&script_path).await
1652 };
1653
1654 if !exists {
1655 continue;
1656 }
1657
1658 let content = {
1660 let ctx = self.exec_ctx.read().await;
1661 match ctx.backend.read(&script_path, None).await {
1662 Ok(bytes) => match String::from_utf8(bytes) {
1663 Ok(s) => s,
1664 Err(e) => {
1665 return Ok(Some(ExecResult::failure(
1666 1,
1667 format!("{}: invalid UTF-8: {}", script_path.display(), e),
1668 )));
1669 }
1670 },
1671 Err(e) => {
1672 return Ok(Some(ExecResult::failure(
1673 1,
1674 format!("{}: {}", script_path.display(), e),
1675 )));
1676 }
1677 }
1678 };
1679
1680 let program = match crate::parser::parse(&content) {
1682 Ok(p) => p,
1683 Err(errors) => {
1684 let msg = errors
1685 .iter()
1686 .map(|e| format!("{}:{}: {}", script_path.display(), e.span.start, e.message))
1687 .collect::<Vec<_>>()
1688 .join("\n");
1689 return Ok(Some(ExecResult::failure(1, msg)));
1690 }
1691 };
1692
1693 let tool_args = self.build_args_async(args).await?;
1695
1696 let mut isolated_scope = Scope::new();
1698
1699 let positional_args: Vec<String> = tool_args.positional
1701 .iter()
1702 .map(value_to_string)
1703 .collect();
1704 isolated_scope.set_positional(name, positional_args);
1705
1706 let original_scope = {
1708 let mut scope = self.scope.write().await;
1709 std::mem::replace(&mut *scope, isolated_scope)
1710 };
1711
1712 let mut result = ExecResult::success("");
1714 for stmt in program.statements {
1715 if matches!(stmt, crate::ast::Stmt::Empty) {
1716 continue;
1717 }
1718
1719 match self.execute_stmt_flow(&stmt).await {
1720 Ok(flow) => {
1721 match flow {
1722 ControlFlow::Normal(r) => result = r,
1723 ControlFlow::Return { value } => {
1724 result = value;
1725 break;
1726 }
1727 ControlFlow::Exit { code } => {
1728 let mut scope = self.scope.write().await;
1730 *scope = original_scope;
1731 return Ok(Some(ExecResult::failure(code, "exit")));
1732 }
1733 ControlFlow::Break { result: r, .. } | ControlFlow::Continue { result: r, .. } => {
1734 result = r;
1735 }
1736 }
1737 }
1738 Err(e) => {
1739 let mut scope = self.scope.write().await;
1741 *scope = original_scope;
1742 return Err(e.context(format!("script: {}", script_path.display())));
1743 }
1744 }
1745 }
1746
1747 {
1749 let mut scope = self.scope.write().await;
1750 *scope = original_scope;
1751 }
1752
1753 return Ok(Some(result));
1754 }
1755
1756 Ok(None)
1758 }
1759
1760 async fn try_execute_external(&self, name: &str, args: &[Arg]) -> Result<Option<ExecResult>> {
1774 if name.contains('/') {
1776 return Ok(None);
1777 }
1778
1779 let path_var = {
1781 let scope = self.scope.read().await;
1782 scope
1783 .get("PATH")
1784 .map(value_to_string)
1785 .unwrap_or_else(|| std::env::var("PATH").unwrap_or_default())
1786 };
1787
1788 let executable = match resolve_in_path(name, &path_var) {
1790 Some(path) => path,
1791 None => return Ok(None), };
1793
1794 let real_cwd = {
1796 let ctx = self.exec_ctx.read().await;
1797 match ctx.backend.resolve_real_path(&ctx.cwd) {
1798 Some(p) => p,
1799 None => {
1800 return Ok(Some(ExecResult::failure(
1801 1,
1802 format!(
1803 "{}: cannot run external command from virtual directory '{}'",
1804 name,
1805 ctx.cwd.display()
1806 ),
1807 )));
1808 }
1809 }
1810 };
1811
1812 let argv = self.build_args_flat(args).await?;
1814
1815 let stdin_data = {
1817 let mut ctx = self.exec_ctx.write().await;
1818 ctx.take_stdin()
1819 };
1820
1821 use tokio::process::Command;
1823
1824 let mut cmd = Command::new(&executable);
1825 cmd.args(&argv);
1826 cmd.current_dir(&real_cwd);
1827
1828 cmd.stdin(if stdin_data.is_some() {
1830 std::process::Stdio::piped()
1831 } else {
1832 std::process::Stdio::null()
1833 });
1834 cmd.stdout(std::process::Stdio::piped());
1835 cmd.stderr(std::process::Stdio::piped());
1836
1837 let mut child = match cmd.spawn() {
1839 Ok(child) => child,
1840 Err(e) => {
1841 return Ok(Some(ExecResult::failure(
1842 127,
1843 format!("{}: {}", name, e),
1844 )));
1845 }
1846 };
1847
1848 if let Some(data) = stdin_data
1850 && let Some(mut stdin) = child.stdin.take()
1851 {
1852 use tokio::io::AsyncWriteExt;
1853 if let Err(e) = stdin.write_all(data.as_bytes()).await {
1854 return Ok(Some(ExecResult::failure(
1855 1,
1856 format!("{}: failed to write stdin: {}", name, e),
1857 )));
1858 }
1859 }
1861
1862 let stdout_stream = Arc::new(BoundedStream::new(DEFAULT_STREAM_MAX_SIZE));
1864 let stderr_stream = Arc::new(BoundedStream::new(DEFAULT_STREAM_MAX_SIZE));
1865
1866 let stdout_pipe = child.stdout.take();
1868 let stderr_pipe = child.stderr.take();
1869
1870 let stdout_clone = stdout_stream.clone();
1871 let stderr_clone = stderr_stream.clone();
1872
1873 let stdout_task = stdout_pipe.map(|pipe| {
1874 tokio::spawn(async move {
1875 drain_to_stream(pipe, stdout_clone).await;
1876 })
1877 });
1878
1879 let stderr_task = stderr_pipe.map(|pipe| {
1880 tokio::spawn(async move {
1881 drain_to_stream(pipe, stderr_clone).await;
1882 })
1883 });
1884
1885 let status = match child.wait().await {
1887 Ok(s) => s,
1888 Err(e) => {
1889 return Ok(Some(ExecResult::failure(
1890 1,
1891 format!("{}: failed to wait: {}", name, e),
1892 )));
1893 }
1894 };
1895
1896 if let Some(task) = stdout_task {
1898 let _ = task.await;
1900 }
1901 if let Some(task) = stderr_task {
1902 let _ = task.await;
1903 }
1904
1905 let code = status.code().unwrap_or_else(|| {
1907 #[cfg(unix)]
1909 {
1910 use std::os::unix::process::ExitStatusExt;
1911 128 + status.signal().unwrap_or(0)
1912 }
1913 #[cfg(not(unix))]
1914 {
1915 -1
1916 }
1917 }) as i64;
1918
1919 let stdout = stdout_stream.read_string().await;
1921 let stderr = stderr_stream.read_string().await;
1922
1923 Ok(Some(ExecResult::from_output(code, stdout, stderr)))
1924 }
1925
1926 pub async fn get_var(&self, name: &str) -> Option<Value> {
1930 let scope = self.scope.read().await;
1931 scope.get(name).cloned()
1932 }
1933
1934 #[cfg(test)]
1936 pub async fn error_exit_enabled(&self) -> bool {
1937 let scope = self.scope.read().await;
1938 scope.error_exit_enabled()
1939 }
1940
1941 pub async fn set_var(&self, name: &str, value: Value) {
1943 let mut scope = self.scope.write().await;
1944 scope.set(name.to_string(), value);
1945 }
1946
1947 pub async fn list_vars(&self) -> Vec<(String, Value)> {
1949 let scope = self.scope.read().await;
1950 scope.all()
1951 }
1952
1953 pub async fn cwd(&self) -> PathBuf {
1957 self.exec_ctx.read().await.cwd.clone()
1958 }
1959
1960 pub async fn set_cwd(&self, path: PathBuf) {
1962 let mut ctx = self.exec_ctx.write().await;
1963 ctx.set_cwd(path);
1964 }
1965
1966 pub async fn last_result(&self) -> ExecResult {
1970 let scope = self.scope.read().await;
1971 scope.last_result().clone()
1972 }
1973
1974 pub fn tool_schemas(&self) -> Vec<crate::tools::ToolSchema> {
1978 self.tools.schemas()
1979 }
1980
1981 pub fn jobs(&self) -> Arc<JobManager> {
1985 self.jobs.clone()
1986 }
1987
1988 pub fn vfs(&self) -> Arc<VfsRouter> {
1992 self.vfs.clone()
1993 }
1994
1995 pub async fn reset(&self) -> Result<()> {
2002 {
2003 let mut scope = self.scope.write().await;
2004 *scope = Scope::new();
2005 }
2006 {
2007 let mut ctx = self.exec_ctx.write().await;
2008 ctx.cwd = PathBuf::from("/");
2009 }
2010 Ok(())
2011 }
2012
2013 pub async fn shutdown(self) -> Result<()> {
2015 self.jobs.wait_all().await;
2017 Ok(())
2018 }
2019}
2020
2021fn accumulate_result(accumulated: &mut ExecResult, new: &ExecResult) {
2027 if !accumulated.out.is_empty() && !new.out.is_empty() {
2028 accumulated.out.push('\n');
2029 }
2030 accumulated.out.push_str(&new.out);
2031 if !accumulated.err.is_empty() && !new.err.is_empty() {
2032 accumulated.err.push('\n');
2033 }
2034 accumulated.err.push_str(&new.err);
2035 accumulated.code = new.code;
2036 accumulated.data = new.data.clone();
2037}
2038
2039fn is_truthy(value: &Value) -> bool {
2041 match value {
2042 Value::Null => false,
2043 Value::Bool(b) => *b,
2044 Value::Int(i) => *i != 0,
2045 Value::Float(f) => *f != 0.0,
2046 Value::String(s) => !s.is_empty(),
2047 Value::Json(json) => match json {
2048 serde_json::Value::Null => false,
2049 serde_json::Value::Array(arr) => !arr.is_empty(),
2050 serde_json::Value::Object(obj) => !obj.is_empty(),
2051 serde_json::Value::Bool(b) => *b,
2052 serde_json::Value::Number(n) => n.as_f64().map(|f| f != 0.0).unwrap_or(false),
2053 serde_json::Value::String(s) => !s.is_empty(),
2054 },
2055 Value::Blob(_) => true, }
2057}
2058
2059fn apply_tilde_expansion(value: Value) -> Value {
2063 match value {
2064 Value::String(s) if s.starts_with('~') => Value::String(expand_tilde(&s)),
2065 _ => value,
2066 }
2067}
2068
2069#[cfg(test)]
2070mod tests {
2071 use super::*;
2072
2073 #[tokio::test]
2074 async fn test_kernel_transient() {
2075 let kernel = Kernel::transient().expect("failed to create kernel");
2076 assert_eq!(kernel.name(), "transient");
2077 }
2078
2079 #[tokio::test]
2080 async fn test_kernel_execute_echo() {
2081 let kernel = Kernel::transient().expect("failed to create kernel");
2082 let result = kernel.execute("echo hello").await.expect("execution failed");
2083 assert!(result.ok());
2084 assert_eq!(result.out.trim(), "hello");
2085 }
2086
2087 #[tokio::test]
2088 async fn test_multiple_statements_accumulate_output() {
2089 let kernel = Kernel::transient().expect("failed to create kernel");
2090 let result = kernel
2091 .execute("echo one\necho two\necho three")
2092 .await
2093 .expect("execution failed");
2094 assert!(result.ok());
2095 assert!(result.out.contains("one"), "missing 'one': {}", result.out);
2097 assert!(result.out.contains("two"), "missing 'two': {}", result.out);
2098 assert!(result.out.contains("three"), "missing 'three': {}", result.out);
2099 }
2100
2101 #[tokio::test]
2102 async fn test_and_chain_accumulates_output() {
2103 let kernel = Kernel::transient().expect("failed to create kernel");
2104 let result = kernel
2105 .execute("echo first && echo second")
2106 .await
2107 .expect("execution failed");
2108 assert!(result.ok());
2109 assert!(result.out.contains("first"), "missing 'first': {}", result.out);
2110 assert!(result.out.contains("second"), "missing 'second': {}", result.out);
2111 }
2112
2113 #[tokio::test]
2114 async fn test_for_loop_accumulates_output() {
2115 let kernel = Kernel::transient().expect("failed to create kernel");
2116 let result = kernel
2117 .execute(r#"for X in a b c; do echo "item: ${X}"; done"#)
2118 .await
2119 .expect("execution failed");
2120 assert!(result.ok());
2121 assert!(result.out.contains("item: a"), "missing 'item: a': {}", result.out);
2122 assert!(result.out.contains("item: b"), "missing 'item: b': {}", result.out);
2123 assert!(result.out.contains("item: c"), "missing 'item: c': {}", result.out);
2124 }
2125
2126 #[tokio::test]
2127 async fn test_while_loop_accumulates_output() {
2128 let kernel = Kernel::transient().expect("failed to create kernel");
2129 let result = kernel
2130 .execute(r#"
2131 N=3
2132 while [[ ${N} -gt 0 ]]; do
2133 echo "N=${N}"
2134 N=$((N - 1))
2135 done
2136 "#)
2137 .await
2138 .expect("execution failed");
2139 assert!(result.ok());
2140 assert!(result.out.contains("N=3"), "missing 'N=3': {}", result.out);
2141 assert!(result.out.contains("N=2"), "missing 'N=2': {}", result.out);
2142 assert!(result.out.contains("N=1"), "missing 'N=1': {}", result.out);
2143 }
2144
2145 #[tokio::test]
2146 async fn test_kernel_set_var() {
2147 let kernel = Kernel::transient().expect("failed to create kernel");
2148
2149 kernel.execute("X=42").await.expect("set failed");
2150
2151 let value = kernel.get_var("X").await;
2152 assert_eq!(value, Some(Value::Int(42)));
2153 }
2154
2155 #[tokio::test]
2156 async fn test_kernel_var_expansion() {
2157 let kernel = Kernel::transient().expect("failed to create kernel");
2158
2159 kernel.execute("NAME=\"world\"").await.expect("set failed");
2160 let result = kernel.execute("echo \"hello ${NAME}\"").await.expect("echo failed");
2161
2162 assert!(result.ok());
2163 assert_eq!(result.out.trim(), "hello world");
2164 }
2165
2166 #[tokio::test]
2167 async fn test_kernel_last_result() {
2168 let kernel = Kernel::transient().expect("failed to create kernel");
2169
2170 kernel.execute("echo test").await.expect("echo failed");
2171
2172 let last = kernel.last_result().await;
2173 assert!(last.ok());
2174 assert_eq!(last.out.trim(), "test");
2175 }
2176
2177 #[tokio::test]
2178 async fn test_kernel_tool_not_found() {
2179 let kernel = Kernel::transient().expect("failed to create kernel");
2180
2181 let result = kernel.execute("nonexistent_tool").await.expect("execution failed");
2182 assert!(!result.ok());
2183 assert_eq!(result.code, 127);
2184 assert!(result.err.contains("command not found"));
2185 }
2186
2187 #[tokio::test]
2188 async fn test_external_command_true() {
2189 let kernel = Kernel::new(KernelConfig::repl()).expect("failed to create kernel");
2191
2192 let result = kernel.execute("true").await.expect("execution failed");
2194 assert!(result.ok(), "true should succeed: {:?}", result);
2196 }
2197
2198 #[tokio::test]
2199 async fn test_external_command_basic() {
2200 let kernel = Kernel::new(KernelConfig::repl()).expect("failed to create kernel");
2202
2203 let path_var = std::env::var("PATH").unwrap_or_default();
2208 eprintln!("System PATH: {}", path_var);
2209
2210 kernel.execute(&format!(r#"PATH="{}""#, path_var)).await.expect("set PATH failed");
2212
2213 let result = kernel.execute("uname").await.expect("execution failed");
2216 eprintln!("uname result: {:?}", result);
2217 assert!(result.ok() || result.code == 127, "uname: {:?}", result);
2219 }
2220
2221 #[tokio::test]
2222 async fn test_kernel_reset() {
2223 let kernel = Kernel::transient().expect("failed to create kernel");
2224
2225 kernel.execute("X=1").await.expect("set failed");
2226 assert!(kernel.get_var("X").await.is_some());
2227
2228 kernel.reset().await.expect("reset failed");
2229 assert!(kernel.get_var("X").await.is_none());
2230 }
2231
2232 #[tokio::test]
2233 async fn test_kernel_cwd() {
2234 let kernel = Kernel::transient().expect("failed to create kernel");
2235
2236 let cwd = kernel.cwd().await;
2238 let home = std::env::var("HOME")
2239 .map(PathBuf::from)
2240 .unwrap_or_else(|_| PathBuf::from("/"));
2241 assert_eq!(cwd, home);
2242
2243 kernel.set_cwd(PathBuf::from("/tmp")).await;
2244 assert_eq!(kernel.cwd().await, PathBuf::from("/tmp"));
2245 }
2246
2247 #[tokio::test]
2248 async fn test_kernel_list_vars() {
2249 let kernel = Kernel::transient().expect("failed to create kernel");
2250
2251 kernel.execute("A=1").await.ok();
2252 kernel.execute("B=2").await.ok();
2253
2254 let vars = kernel.list_vars().await;
2255 assert!(vars.iter().any(|(n, v)| n == "A" && *v == Value::Int(1)));
2256 assert!(vars.iter().any(|(n, v)| n == "B" && *v == Value::Int(2)));
2257 }
2258
2259 #[tokio::test]
2260 async fn test_is_truthy() {
2261 assert!(!is_truthy(&Value::Null));
2262 assert!(!is_truthy(&Value::Bool(false)));
2263 assert!(is_truthy(&Value::Bool(true)));
2264 assert!(!is_truthy(&Value::Int(0)));
2265 assert!(is_truthy(&Value::Int(1)));
2266 assert!(!is_truthy(&Value::String("".into())));
2267 assert!(is_truthy(&Value::String("x".into())));
2268 }
2269
2270 #[tokio::test]
2271 async fn test_jq_in_pipeline() {
2272 let kernel = Kernel::transient().expect("failed to create kernel");
2273 let result = kernel
2275 .execute(r#"echo "{\"name\": \"Alice\"}" | jq ".name" -r"#)
2276 .await
2277 .expect("execution failed");
2278 assert!(result.ok(), "jq pipeline failed: {}", result.err);
2279 assert_eq!(result.out.trim(), "Alice");
2280 }
2281
2282 #[tokio::test]
2283 async fn test_user_defined_tool() {
2284 let kernel = Kernel::transient().expect("failed to create kernel");
2285
2286 kernel
2288 .execute(r#"greet() { echo "Hello, $1!" }"#)
2289 .await
2290 .expect("function definition failed");
2291
2292 let result = kernel
2294 .execute(r#"greet "World""#)
2295 .await
2296 .expect("function call failed");
2297
2298 assert!(result.ok(), "greet failed: {}", result.err);
2299 assert_eq!(result.out.trim(), "Hello, World!");
2300 }
2301
2302 #[tokio::test]
2303 async fn test_user_tool_positional_args() {
2304 let kernel = Kernel::transient().expect("failed to create kernel");
2305
2306 kernel
2308 .execute(r#"greet() { echo "Hi $1" }"#)
2309 .await
2310 .expect("function definition failed");
2311
2312 let result = kernel
2314 .execute(r#"greet "Amy""#)
2315 .await
2316 .expect("function call failed");
2317
2318 assert!(result.ok(), "greet failed: {}", result.err);
2319 assert_eq!(result.out.trim(), "Hi Amy");
2320 }
2321
2322 #[tokio::test]
2323 async fn test_function_shared_scope() {
2324 let kernel = Kernel::transient().expect("failed to create kernel");
2325
2326 kernel
2328 .execute(r#"SECRET="hidden""#)
2329 .await
2330 .expect("set failed");
2331
2332 kernel
2334 .execute(r#"access_parent() {
2335 echo "${SECRET}"
2336 SECRET="modified"
2337 }"#)
2338 .await
2339 .expect("function definition failed");
2340
2341 let result = kernel.execute("access_parent").await.expect("function call failed");
2343
2344 assert!(
2346 result.out.contains("hidden"),
2347 "Function should access parent scope, got: {}",
2348 result.out
2349 );
2350
2351 let secret = kernel.get_var("SECRET").await;
2353 assert_eq!(
2354 secret,
2355 Some(Value::String("modified".into())),
2356 "Function should modify parent scope"
2357 );
2358 }
2359
2360 #[tokio::test]
2361 async fn test_exec_builtin() {
2362 let kernel = Kernel::transient().expect("failed to create kernel");
2363 let result = kernel
2365 .execute(r#"exec command="/bin/echo" argv="hello world""#)
2366 .await
2367 .expect("exec failed");
2368
2369 assert!(result.ok(), "exec failed: {}", result.err);
2370 assert_eq!(result.out.trim(), "hello world");
2371 }
2372
2373 #[tokio::test]
2374 async fn test_while_false_never_runs() {
2375 let kernel = Kernel::transient().expect("failed to create kernel");
2376
2377 let result = kernel
2379 .execute(r#"
2380 while false; do
2381 echo "should not run"
2382 done
2383 "#)
2384 .await
2385 .expect("while false failed");
2386
2387 assert!(result.ok());
2388 assert!(result.out.is_empty(), "while false should not execute body: {}", result.out);
2389 }
2390
2391 #[tokio::test]
2392 async fn test_while_string_comparison() {
2393 let kernel = Kernel::transient().expect("failed to create kernel");
2394
2395 kernel.execute(r#"FLAG="go""#).await.expect("set failed");
2397
2398 let result = kernel
2401 .execute(r#"
2402 while [[ ${FLAG} == "go" ]]; do
2403 FLAG="stop"
2404 echo "running"
2405 done
2406 "#)
2407 .await
2408 .expect("while with string cmp failed");
2409
2410 assert!(result.ok());
2411 assert!(result.out.contains("running"), "should have run once: {}", result.out);
2412
2413 let flag = kernel.get_var("FLAG").await;
2415 assert_eq!(flag, Some(Value::String("stop".into())));
2416 }
2417
2418 #[tokio::test]
2419 async fn test_while_numeric_comparison() {
2420 let kernel = Kernel::transient().expect("failed to create kernel");
2421
2422 kernel.execute("N=5").await.expect("set failed");
2424
2425 let result = kernel
2427 .execute(r#"
2428 while [[ ${N} -gt 3 ]]; do
2429 N=3
2430 echo "N was greater"
2431 done
2432 "#)
2433 .await
2434 .expect("while with > failed");
2435
2436 assert!(result.ok());
2437 assert!(result.out.contains("N was greater"), "should have run once: {}", result.out);
2438 }
2439
2440 #[tokio::test]
2441 async fn test_break_in_while_loop() {
2442 let kernel = Kernel::transient().expect("failed to create kernel");
2443
2444 let result = kernel
2445 .execute(r#"
2446 I=0
2447 while true; do
2448 I=1
2449 echo "before break"
2450 break
2451 echo "after break"
2452 done
2453 "#)
2454 .await
2455 .expect("while with break failed");
2456
2457 assert!(result.ok());
2458 assert!(result.out.contains("before break"), "should see before break: {}", result.out);
2459 assert!(!result.out.contains("after break"), "should not see after break: {}", result.out);
2460
2461 let i = kernel.get_var("I").await;
2463 assert_eq!(i, Some(Value::Int(1)));
2464 }
2465
2466 #[tokio::test]
2467 async fn test_continue_in_while_loop() {
2468 let kernel = Kernel::transient().expect("failed to create kernel");
2469
2470 let result = kernel
2475 .execute(r#"
2476 STATE="start"
2477 AFTER_CONTINUE="no"
2478 while [[ ${STATE} != "done" ]]; do
2479 if [[ ${STATE} == "start" ]]; then
2480 STATE="middle"
2481 continue
2482 AFTER_CONTINUE="yes"
2483 fi
2484 if [[ ${STATE} == "middle" ]]; then
2485 STATE="done"
2486 fi
2487 done
2488 "#)
2489 .await
2490 .expect("while with continue failed");
2491
2492 assert!(result.ok());
2493
2494 let state = kernel.get_var("STATE").await;
2496 assert_eq!(state, Some(Value::String("done".into())));
2497
2498 let after = kernel.get_var("AFTER_CONTINUE").await;
2500 assert_eq!(after, Some(Value::String("no".into())));
2501 }
2502
2503 #[tokio::test]
2504 async fn test_break_with_level() {
2505 let kernel = Kernel::transient().expect("failed to create kernel");
2506
2507 let result = kernel
2512 .execute(r#"
2513 OUTER=0
2514 while true; do
2515 OUTER=1
2516 for X in "1 2"; do
2517 break 2
2518 done
2519 OUTER=2
2520 done
2521 "#)
2522 .await
2523 .expect("nested break failed");
2524
2525 assert!(result.ok());
2526
2527 let outer = kernel.get_var("OUTER").await;
2529 assert_eq!(outer, Some(Value::Int(1)), "break 2 should have skipped OUTER=2");
2530 }
2531
2532 #[tokio::test]
2533 async fn test_return_from_tool() {
2534 let kernel = Kernel::transient().expect("failed to create kernel");
2535
2536 kernel
2538 .execute(r#"early_return() {
2539 if [[ $1 == 1 ]]; then
2540 return 42
2541 fi
2542 echo "not returned"
2543 }"#)
2544 .await
2545 .expect("function definition failed");
2546
2547 let result = kernel
2550 .execute("early_return 1")
2551 .await
2552 .expect("function call failed");
2553
2554 assert_eq!(result.code, 42);
2556 assert!(result.out.is_empty());
2558 }
2559
2560 #[tokio::test]
2561 async fn test_return_without_value() {
2562 let kernel = Kernel::transient().expect("failed to create kernel");
2563
2564 kernel
2566 .execute(r#"early_exit() {
2567 if [[ $1 == "stop" ]]; then
2568 return
2569 fi
2570 echo "continued"
2571 }"#)
2572 .await
2573 .expect("function definition failed");
2574
2575 let result = kernel
2577 .execute(r#"early_exit "stop""#)
2578 .await
2579 .expect("function call failed");
2580
2581 assert!(result.ok());
2582 assert!(result.out.is_empty() || result.out.trim().is_empty());
2583 }
2584
2585 #[tokio::test]
2586 async fn test_exit_stops_execution() {
2587 let kernel = Kernel::transient().expect("failed to create kernel");
2588
2589 kernel
2591 .execute(r#"
2592 BEFORE="yes"
2593 exit 0
2594 AFTER="yes"
2595 "#)
2596 .await
2597 .expect("execution failed");
2598
2599 let before = kernel.get_var("BEFORE").await;
2601 assert_eq!(before, Some(Value::String("yes".into())));
2602
2603 let after = kernel.get_var("AFTER").await;
2604 assert!(after.is_none(), "AFTER should not be set after exit");
2605 }
2606
2607 #[tokio::test]
2608 async fn test_exit_with_code() {
2609 let kernel = Kernel::transient().expect("failed to create kernel");
2610
2611 let result = kernel
2613 .execute("exit 42")
2614 .await
2615 .expect("exit failed");
2616
2617 assert_eq!(result.out, "42");
2619 }
2620
2621 #[tokio::test]
2622 async fn test_set_e_stops_on_failure() {
2623 let kernel = Kernel::transient().expect("failed to create kernel");
2624
2625 kernel.execute("set -e").await.expect("set -e failed");
2627
2628 kernel
2630 .execute(r#"
2631 STEP1="done"
2632 false
2633 STEP2="done"
2634 "#)
2635 .await
2636 .expect("execution failed");
2637
2638 let step1 = kernel.get_var("STEP1").await;
2640 assert_eq!(step1, Some(Value::String("done".into())));
2641
2642 let step2 = kernel.get_var("STEP2").await;
2643 assert!(step2.is_none(), "STEP2 should not be set after false with set -e");
2644 }
2645
2646 #[tokio::test]
2647 async fn test_set_plus_e_disables_error_exit() {
2648 let kernel = Kernel::transient().expect("failed to create kernel");
2649
2650 kernel.execute("set -e").await.expect("set -e failed");
2652 kernel.execute("set +e").await.expect("set +e failed");
2653
2654 kernel
2656 .execute(r#"
2657 STEP1="done"
2658 false
2659 STEP2="done"
2660 "#)
2661 .await
2662 .expect("execution failed");
2663
2664 let step1 = kernel.get_var("STEP1").await;
2666 assert_eq!(step1, Some(Value::String("done".into())));
2667
2668 let step2 = kernel.get_var("STEP2").await;
2669 assert_eq!(step2, Some(Value::String("done".into())));
2670 }
2671
2672 #[tokio::test]
2673 async fn test_set_ignores_unknown_options() {
2674 let kernel = Kernel::transient().expect("failed to create kernel");
2675
2676 let result = kernel
2678 .execute("set -e -u -o pipefail")
2679 .await
2680 .expect("set with unknown options failed");
2681
2682 assert!(result.ok(), "set should succeed with unknown options");
2683
2684 kernel
2686 .execute(r#"
2687 BEFORE="yes"
2688 false
2689 AFTER="yes"
2690 "#)
2691 .await
2692 .ok();
2693
2694 let after = kernel.get_var("AFTER").await;
2695 assert!(after.is_none(), "-e should be enabled despite unknown options");
2696 }
2697
2698 #[tokio::test]
2699 async fn test_set_no_args_shows_settings() {
2700 let kernel = Kernel::transient().expect("failed to create kernel");
2701
2702 kernel.execute("set -e").await.expect("set -e failed");
2704
2705 let result = kernel.execute("set").await.expect("set failed");
2707
2708 assert!(result.ok());
2709 assert!(result.out.contains("set -e"), "should show -e is enabled: {}", result.out);
2710 }
2711
2712 #[tokio::test]
2713 async fn test_set_e_in_pipeline() {
2714 let kernel = Kernel::transient().expect("failed to create kernel");
2715
2716 kernel.execute("set -e").await.expect("set -e failed");
2717
2718 kernel
2720 .execute(r#"
2721 BEFORE="yes"
2722 false | cat
2723 AFTER="yes"
2724 "#)
2725 .await
2726 .ok();
2727
2728 let before = kernel.get_var("BEFORE").await;
2729 assert_eq!(before, Some(Value::String("yes".into())));
2730
2731 }
2736
2737 #[tokio::test]
2738 async fn test_set_e_with_and_chain() {
2739 let kernel = Kernel::transient().expect("failed to create kernel");
2740
2741 kernel.execute("set -e").await.expect("set -e failed");
2742
2743 kernel
2746 .execute(r#"
2747 RESULT="initial"
2748 false && RESULT="chained"
2749 RESULT="continued"
2750 "#)
2751 .await
2752 .ok();
2753
2754 let result = kernel.get_var("RESULT").await;
2757 assert!(result.is_some(), "RESULT should be set");
2760 }
2761
2762 #[tokio::test]
2767 async fn test_source_sets_variables() {
2768 let kernel = Kernel::transient().expect("failed to create kernel");
2769
2770 kernel
2772 .execute(r#"write "/test.kai" 'FOO="bar"'"#)
2773 .await
2774 .expect("write failed");
2775
2776 let result = kernel
2778 .execute(r#"source "/test.kai""#)
2779 .await
2780 .expect("source failed");
2781
2782 assert!(result.ok(), "source should succeed");
2783
2784 let foo = kernel.get_var("FOO").await;
2786 assert_eq!(foo, Some(Value::String("bar".into())));
2787 }
2788
2789 #[tokio::test]
2790 async fn test_source_with_dot_alias() {
2791 let kernel = Kernel::transient().expect("failed to create kernel");
2792
2793 kernel
2795 .execute(r#"write "/vars.kai" 'X=42'"#)
2796 .await
2797 .expect("write failed");
2798
2799 let result = kernel
2801 .execute(r#". "/vars.kai""#)
2802 .await
2803 .expect(". failed");
2804
2805 assert!(result.ok(), ". should succeed");
2806
2807 let x = kernel.get_var("X").await;
2809 assert_eq!(x, Some(Value::Int(42)));
2810 }
2811
2812 #[tokio::test]
2813 async fn test_source_not_found() {
2814 let kernel = Kernel::transient().expect("failed to create kernel");
2815
2816 let result = kernel
2818 .execute(r#"source "/nonexistent.kai""#)
2819 .await
2820 .expect("source should not fail with error");
2821
2822 assert!(!result.ok(), "source of non-existent file should fail");
2823 assert!(result.err.contains("nonexistent.kai"), "error should mention filename");
2824 }
2825
2826 #[tokio::test]
2827 async fn test_source_missing_filename() {
2828 let kernel = Kernel::transient().expect("failed to create kernel");
2829
2830 let result = kernel
2832 .execute("source")
2833 .await
2834 .expect("source should not fail with error");
2835
2836 assert!(!result.ok(), "source without filename should fail");
2837 assert!(result.err.contains("missing filename"), "error should mention missing filename");
2838 }
2839
2840 #[tokio::test]
2841 async fn test_source_executes_multiple_statements() {
2842 let kernel = Kernel::transient().expect("failed to create kernel");
2843
2844 kernel
2846 .execute(r#"write "/multi.kai" 'A=1
2847B=2
2848C=3'"#)
2849 .await
2850 .expect("write failed");
2851
2852 kernel
2854 .execute(r#"source "/multi.kai""#)
2855 .await
2856 .expect("source failed");
2857
2858 assert_eq!(kernel.get_var("A").await, Some(Value::Int(1)));
2860 assert_eq!(kernel.get_var("B").await, Some(Value::Int(2)));
2861 assert_eq!(kernel.get_var("C").await, Some(Value::Int(3)));
2862 }
2863
2864 #[tokio::test]
2865 async fn test_source_can_define_functions() {
2866 let kernel = Kernel::transient().expect("failed to create kernel");
2867
2868 kernel
2870 .execute(r#"write "/functions.kai" 'greet() {
2871 echo "Hello, $1!"
2872}'"#)
2873 .await
2874 .expect("write failed");
2875
2876 kernel
2878 .execute(r#"source "/functions.kai""#)
2879 .await
2880 .expect("source failed");
2881
2882 let result = kernel
2884 .execute(r#"greet "World""#)
2885 .await
2886 .expect("greet failed");
2887
2888 assert!(result.ok());
2889 assert!(result.out.contains("Hello, World!"));
2890 }
2891
2892 #[tokio::test]
2893 async fn test_source_inherits_error_exit() {
2894 let kernel = Kernel::transient().expect("failed to create kernel");
2895
2896 kernel.execute("set -e").await.expect("set -e failed");
2898
2899 kernel
2901 .execute(r#"write "/fail.kai" 'BEFORE="yes"
2902false
2903AFTER="yes"'"#)
2904 .await
2905 .expect("write failed");
2906
2907 kernel
2909 .execute(r#"source "/fail.kai""#)
2910 .await
2911 .ok();
2912
2913 let before = kernel.get_var("BEFORE").await;
2915 assert_eq!(before, Some(Value::String("yes".into())));
2916
2917 }
2920
2921 #[tokio::test]
2926 async fn test_case_simple_match() {
2927 let kernel = Kernel::transient().expect("failed to create kernel");
2928
2929 let result = kernel
2930 .execute(r#"
2931 case "hello" in
2932 hello) echo "matched hello" ;;
2933 world) echo "matched world" ;;
2934 esac
2935 "#)
2936 .await
2937 .expect("case failed");
2938
2939 assert!(result.ok());
2940 assert_eq!(result.out.trim(), "matched hello");
2941 }
2942
2943 #[tokio::test]
2944 async fn test_case_wildcard_match() {
2945 let kernel = Kernel::transient().expect("failed to create kernel");
2946
2947 let result = kernel
2948 .execute(r#"
2949 case "main.rs" in
2950 "*.py") echo "Python" ;;
2951 "*.rs") echo "Rust" ;;
2952 "*") echo "Unknown" ;;
2953 esac
2954 "#)
2955 .await
2956 .expect("case failed");
2957
2958 assert!(result.ok());
2959 assert_eq!(result.out.trim(), "Rust");
2960 }
2961
2962 #[tokio::test]
2963 async fn test_case_default_match() {
2964 let kernel = Kernel::transient().expect("failed to create kernel");
2965
2966 let result = kernel
2967 .execute(r#"
2968 case "unknown.xyz" in
2969 "*.py") echo "Python" ;;
2970 "*.rs") echo "Rust" ;;
2971 "*") echo "Default" ;;
2972 esac
2973 "#)
2974 .await
2975 .expect("case failed");
2976
2977 assert!(result.ok());
2978 assert_eq!(result.out.trim(), "Default");
2979 }
2980
2981 #[tokio::test]
2982 async fn test_case_no_match() {
2983 let kernel = Kernel::transient().expect("failed to create kernel");
2984
2985 let result = kernel
2987 .execute(r#"
2988 case "nope" in
2989 "yes") echo "yes" ;;
2990 "no") echo "no" ;;
2991 esac
2992 "#)
2993 .await
2994 .expect("case failed");
2995
2996 assert!(result.ok());
2997 assert!(result.out.is_empty(), "no match should produce empty output");
2998 }
2999
3000 #[tokio::test]
3001 async fn test_case_with_variable() {
3002 let kernel = Kernel::transient().expect("failed to create kernel");
3003
3004 kernel.execute(r#"LANG="rust""#).await.expect("set failed");
3005
3006 let result = kernel
3007 .execute(r#"
3008 case ${LANG} in
3009 python) echo "snake" ;;
3010 rust) echo "crab" ;;
3011 go) echo "gopher" ;;
3012 esac
3013 "#)
3014 .await
3015 .expect("case failed");
3016
3017 assert!(result.ok());
3018 assert_eq!(result.out.trim(), "crab");
3019 }
3020
3021 #[tokio::test]
3022 async fn test_case_multiple_patterns() {
3023 let kernel = Kernel::transient().expect("failed to create kernel");
3024
3025 let result = kernel
3026 .execute(r#"
3027 case "yes" in
3028 "y"|"yes"|"Y"|"YES") echo "affirmative" ;;
3029 "n"|"no"|"N"|"NO") echo "negative" ;;
3030 esac
3031 "#)
3032 .await
3033 .expect("case failed");
3034
3035 assert!(result.ok());
3036 assert_eq!(result.out.trim(), "affirmative");
3037 }
3038
3039 #[tokio::test]
3040 async fn test_case_glob_question_mark() {
3041 let kernel = Kernel::transient().expect("failed to create kernel");
3042
3043 let result = kernel
3044 .execute(r#"
3045 case "test1" in
3046 "test?") echo "matched test?" ;;
3047 "*") echo "default" ;;
3048 esac
3049 "#)
3050 .await
3051 .expect("case failed");
3052
3053 assert!(result.ok());
3054 assert_eq!(result.out.trim(), "matched test?");
3055 }
3056
3057 #[tokio::test]
3058 async fn test_case_char_class() {
3059 let kernel = Kernel::transient().expect("failed to create kernel");
3060
3061 let result = kernel
3062 .execute(r#"
3063 case "Yes" in
3064 "[Yy]*") echo "yes-like" ;;
3065 "[Nn]*") echo "no-like" ;;
3066 esac
3067 "#)
3068 .await
3069 .expect("case failed");
3070
3071 assert!(result.ok());
3072 assert_eq!(result.out.trim(), "yes-like");
3073 }
3074
3075 #[tokio::test]
3080 async fn test_cat_from_pipeline() {
3081 let kernel = Kernel::transient().expect("failed to create kernel");
3082
3083 let result = kernel
3084 .execute(r#"echo "piped text" | cat"#)
3085 .await
3086 .expect("cat pipeline failed");
3087
3088 assert!(result.ok(), "cat failed: {}", result.err);
3089 assert_eq!(result.out.trim(), "piped text");
3090 }
3091
3092 #[tokio::test]
3093 async fn test_cat_from_pipeline_multiline() {
3094 let kernel = Kernel::transient().expect("failed to create kernel");
3095
3096 let result = kernel
3097 .execute(r#"echo "line1\nline2" | cat -n"#)
3098 .await
3099 .expect("cat pipeline failed");
3100
3101 assert!(result.ok(), "cat failed: {}", result.err);
3102 assert!(result.out.contains("1\t"), "output: {}", result.out);
3103 }
3104
3105 #[tokio::test]
3110 async fn test_heredoc_basic() {
3111 let kernel = Kernel::transient().expect("failed to create kernel");
3112
3113 let result = kernel
3114 .execute("cat <<EOF\nhello\nEOF")
3115 .await
3116 .expect("heredoc failed");
3117
3118 assert!(result.ok(), "cat with heredoc failed: {}", result.err);
3119 assert_eq!(result.out.trim(), "hello");
3120 }
3121
3122 #[tokio::test]
3123 async fn test_arithmetic_in_string() {
3124 let kernel = Kernel::transient().expect("failed to create kernel");
3125
3126 let result = kernel
3127 .execute(r#"echo "result: $((1 + 2))""#)
3128 .await
3129 .expect("arithmetic in string failed");
3130
3131 assert!(result.ok(), "echo failed: {}", result.err);
3132 assert_eq!(result.out.trim(), "result: 3");
3133 }
3134
3135 #[tokio::test]
3136 async fn test_heredoc_multiline() {
3137 let kernel = Kernel::transient().expect("failed to create kernel");
3138
3139 let result = kernel
3140 .execute("cat <<EOF\nline1\nline2\nline3\nEOF")
3141 .await
3142 .expect("heredoc failed");
3143
3144 assert!(result.ok(), "cat with heredoc failed: {}", result.err);
3145 assert!(result.out.contains("line1"), "output: {}", result.out);
3146 assert!(result.out.contains("line2"), "output: {}", result.out);
3147 assert!(result.out.contains("line3"), "output: {}", result.out);
3148 }
3149
3150 #[tokio::test]
3155 async fn test_read_from_pipeline() {
3156 let kernel = Kernel::transient().expect("failed to create kernel");
3157
3158 let result = kernel
3160 .execute(r#"echo "Alice" | read NAME; echo "Hello, ${NAME}""#)
3161 .await
3162 .expect("read pipeline failed");
3163
3164 assert!(result.ok(), "read failed: {}", result.err);
3165 assert!(result.out.contains("Hello, Alice"), "output: {}", result.out);
3166 }
3167
3168 #[tokio::test]
3169 async fn test_read_multiple_vars_from_pipeline() {
3170 let kernel = Kernel::transient().expect("failed to create kernel");
3171
3172 let result = kernel
3173 .execute(r#"echo "John Doe 42" | read FIRST LAST AGE; echo "${FIRST} is ${AGE}""#)
3174 .await
3175 .expect("read pipeline failed");
3176
3177 assert!(result.ok(), "read failed: {}", result.err);
3178 assert!(result.out.contains("John is 42"), "output: {}", result.out);
3179 }
3180
3181 #[tokio::test]
3186 async fn test_posix_function_with_positional_params() {
3187 let kernel = Kernel::transient().expect("failed to create kernel");
3188
3189 kernel
3191 .execute(r#"greet() { echo "Hello, $1!" }"#)
3192 .await
3193 .expect("function definition failed");
3194
3195 let result = kernel
3197 .execute(r#"greet "Amy""#)
3198 .await
3199 .expect("function call failed");
3200
3201 assert!(result.ok(), "greet failed: {}", result.err);
3202 assert_eq!(result.out.trim(), "Hello, Amy!");
3203 }
3204
3205 #[tokio::test]
3206 async fn test_posix_function_multiple_args() {
3207 let kernel = Kernel::transient().expect("failed to create kernel");
3208
3209 kernel
3211 .execute(r#"add_greeting() { echo "$1 $2!" }"#)
3212 .await
3213 .expect("function definition failed");
3214
3215 let result = kernel
3217 .execute(r#"add_greeting "Hello" "World""#)
3218 .await
3219 .expect("function call failed");
3220
3221 assert!(result.ok(), "function failed: {}", result.err);
3222 assert_eq!(result.out.trim(), "Hello World!");
3223 }
3224
3225 #[tokio::test]
3226 async fn test_bash_function_with_positional_params() {
3227 let kernel = Kernel::transient().expect("failed to create kernel");
3228
3229 kernel
3231 .execute(r#"function greet { echo "Hi $1" }"#)
3232 .await
3233 .expect("function definition failed");
3234
3235 let result = kernel
3237 .execute(r#"greet "Bob""#)
3238 .await
3239 .expect("function call failed");
3240
3241 assert!(result.ok(), "greet failed: {}", result.err);
3242 assert_eq!(result.out.trim(), "Hi Bob");
3243 }
3244
3245 #[tokio::test]
3246 async fn test_shell_function_with_all_args() {
3247 let kernel = Kernel::transient().expect("failed to create kernel");
3248
3249 kernel
3251 .execute(r#"echo_all() { echo "args: $@" }"#)
3252 .await
3253 .expect("function definition failed");
3254
3255 let result = kernel
3257 .execute(r#"echo_all "a" "b" "c""#)
3258 .await
3259 .expect("function call failed");
3260
3261 assert!(result.ok(), "function failed: {}", result.err);
3262 assert_eq!(result.out.trim(), "args: a b c");
3263 }
3264
3265 #[tokio::test]
3266 async fn test_shell_function_with_arg_count() {
3267 let kernel = Kernel::transient().expect("failed to create kernel");
3268
3269 kernel
3271 .execute(r#"count_args() { echo "count: $#" }"#)
3272 .await
3273 .expect("function definition failed");
3274
3275 let result = kernel
3277 .execute(r#"count_args "x" "y" "z""#)
3278 .await
3279 .expect("function call failed");
3280
3281 assert!(result.ok(), "function failed: {}", result.err);
3282 assert_eq!(result.out.trim(), "count: 3");
3283 }
3284
3285 #[tokio::test]
3286 async fn test_shell_function_shared_scope() {
3287 let kernel = Kernel::transient().expect("failed to create kernel");
3288
3289 kernel
3291 .execute(r#"PARENT_VAR="visible""#)
3292 .await
3293 .expect("set failed");
3294
3295 kernel
3297 .execute(r#"modify_parent() {
3298 echo "saw: ${PARENT_VAR}"
3299 PARENT_VAR="changed by function"
3300 }"#)
3301 .await
3302 .expect("function definition failed");
3303
3304 let result = kernel.execute("modify_parent").await.expect("function failed");
3306
3307 assert!(
3308 result.out.contains("visible"),
3309 "Shell function should access parent scope, got: {}",
3310 result.out
3311 );
3312
3313 let var = kernel.get_var("PARENT_VAR").await;
3315 assert_eq!(
3316 var,
3317 Some(Value::String("changed by function".into())),
3318 "Shell function should modify parent scope"
3319 );
3320 }
3321
3322 #[tokio::test]
3327 async fn test_script_execution_from_path() {
3328 let kernel = Kernel::transient().expect("failed to create kernel");
3329
3330 kernel.execute(r#"mkdir "/bin""#).await.ok();
3332 kernel
3333 .execute(r#"write "/bin/hello.kai" 'echo "Hello from script!"'"#)
3334 .await
3335 .expect("write script failed");
3336
3337 kernel.execute(r#"PATH="/bin""#).await.expect("set PATH failed");
3339
3340 let result = kernel
3342 .execute("hello")
3343 .await
3344 .expect("script execution failed");
3345
3346 assert!(result.ok(), "script failed: {}", result.err);
3347 assert_eq!(result.out.trim(), "Hello from script!");
3348 }
3349
3350 #[tokio::test]
3351 async fn test_script_with_args() {
3352 let kernel = Kernel::transient().expect("failed to create kernel");
3353
3354 kernel.execute(r#"mkdir "/bin""#).await.ok();
3356 kernel
3357 .execute(r#"write "/bin/greet.kai" 'echo "Hello, $1!"'"#)
3358 .await
3359 .expect("write script failed");
3360
3361 kernel.execute(r#"PATH="/bin""#).await.expect("set PATH failed");
3363
3364 let result = kernel
3366 .execute(r#"greet "World""#)
3367 .await
3368 .expect("script execution failed");
3369
3370 assert!(result.ok(), "script failed: {}", result.err);
3371 assert_eq!(result.out.trim(), "Hello, World!");
3372 }
3373
3374 #[tokio::test]
3375 async fn test_script_not_found() {
3376 let kernel = Kernel::transient().expect("failed to create kernel");
3377
3378 kernel.execute(r#"PATH="/nonexistent""#).await.expect("set PATH failed");
3380
3381 let result = kernel
3383 .execute("noscript")
3384 .await
3385 .expect("execution failed");
3386
3387 assert!(!result.ok(), "should fail with command not found");
3388 assert_eq!(result.code, 127);
3389 assert!(result.err.contains("command not found"));
3390 }
3391
3392 #[tokio::test]
3393 async fn test_script_path_search_order() {
3394 let kernel = Kernel::transient().expect("failed to create kernel");
3395
3396 kernel.execute(r#"mkdir "/first""#).await.ok();
3399 kernel.execute(r#"mkdir "/second""#).await.ok();
3400 kernel
3401 .execute(r#"write "/first/myscript.kai" 'echo "from first"'"#)
3402 .await
3403 .expect("write failed");
3404 kernel
3405 .execute(r#"write "/second/myscript.kai" 'echo "from second"'"#)
3406 .await
3407 .expect("write failed");
3408
3409 kernel.execute(r#"PATH="/first:/second""#).await.expect("set PATH failed");
3411
3412 let result = kernel
3414 .execute("myscript")
3415 .await
3416 .expect("script execution failed");
3417
3418 assert!(result.ok(), "script failed: {}", result.err);
3419 assert_eq!(result.out.trim(), "from first");
3420 }
3421
3422 #[tokio::test]
3427 async fn test_last_exit_code_success() {
3428 let kernel = Kernel::transient().expect("failed to create kernel");
3429
3430 let result = kernel.execute("true; echo $?").await.expect("execution failed");
3432 assert!(result.out.contains("0"), "expected 0, got: {}", result.out);
3433 }
3434
3435 #[tokio::test]
3436 async fn test_last_exit_code_failure() {
3437 let kernel = Kernel::transient().expect("failed to create kernel");
3438
3439 let result = kernel.execute("false; echo $?").await.expect("execution failed");
3441 assert!(result.out.contains("1"), "expected 1, got: {}", result.out);
3442 }
3443
3444 #[tokio::test]
3445 async fn test_current_pid() {
3446 let kernel = Kernel::transient().expect("failed to create kernel");
3447
3448 let result = kernel.execute("echo $$").await.expect("execution failed");
3449 let pid: u32 = result.out.trim().parse().expect("PID should be a number");
3451 assert!(pid > 0, "PID should be positive");
3452 }
3453
3454 #[tokio::test]
3455 async fn test_unset_variable_expands_to_empty() {
3456 let kernel = Kernel::transient().expect("failed to create kernel");
3457
3458 let result = kernel.execute(r#"echo "prefix:${UNSET_VAR}:suffix""#).await.expect("execution failed");
3460 assert_eq!(result.out.trim(), "prefix::suffix");
3461 }
3462
3463 #[tokio::test]
3464 async fn test_eq_ne_operators() {
3465 let kernel = Kernel::transient().expect("failed to create kernel");
3466
3467 let result = kernel.execute(r#"if [[ 5 -eq 5 ]]; then echo "eq works"; fi"#).await.expect("execution failed");
3469 assert_eq!(result.out.trim(), "eq works");
3470
3471 let result = kernel.execute(r#"if [[ 5 -ne 3 ]]; then echo "ne works"; fi"#).await.expect("execution failed");
3473 assert_eq!(result.out.trim(), "ne works");
3474
3475 let result = kernel.execute(r#"if [[ 5 -eq 3 ]]; then echo "wrong"; else echo "correct"; fi"#).await.expect("execution failed");
3477 assert_eq!(result.out.trim(), "correct");
3478 }
3479
3480 #[tokio::test]
3481 async fn test_escaped_dollar_in_string() {
3482 let kernel = Kernel::transient().expect("failed to create kernel");
3483
3484 let result = kernel.execute(r#"echo "\$100""#).await.expect("execution failed");
3486 assert_eq!(result.out.trim(), "$100");
3487 }
3488
3489 #[tokio::test]
3490 async fn test_special_vars_in_interpolation() {
3491 let kernel = Kernel::transient().expect("failed to create kernel");
3492
3493 let result = kernel.execute(r#"true; echo "exit: $?""#).await.expect("execution failed");
3495 assert_eq!(result.out.trim(), "exit: 0");
3496
3497 let result = kernel.execute(r#"echo "pid: $$""#).await.expect("execution failed");
3499 assert!(result.out.starts_with("pid: "), "unexpected output: {}", result.out);
3500 let pid_part = result.out.trim().strip_prefix("pid: ").unwrap();
3501 let _pid: u32 = pid_part.parse().expect("PID in string should be a number");
3502 }
3503
3504 #[tokio::test]
3509 async fn test_command_subst_assignment() {
3510 let kernel = Kernel::transient().expect("failed to create kernel");
3511
3512 let result = kernel.execute(r#"X=$(echo hello); echo "$X""#).await.expect("execution failed");
3514 assert_eq!(result.out.trim(), "hello");
3515 }
3516
3517 #[tokio::test]
3518 async fn test_command_subst_with_args() {
3519 let kernel = Kernel::transient().expect("failed to create kernel");
3520
3521 let result = kernel.execute(r#"X=$(echo "a b c"); echo "$X""#).await.expect("execution failed");
3523 assert_eq!(result.out.trim(), "a b c");
3524 }
3525
3526 #[tokio::test]
3527 async fn test_command_subst_nested_vars() {
3528 let kernel = Kernel::transient().expect("failed to create kernel");
3529
3530 let result = kernel.execute(r#"Y=world; X=$(echo "hello $Y"); echo "$X""#).await.expect("execution failed");
3532 assert_eq!(result.out.trim(), "hello world");
3533 }
3534
3535 #[tokio::test]
3536 async fn test_background_job_basic() {
3537 use std::time::Duration;
3538
3539 let kernel = Kernel::new(KernelConfig::isolated()).expect("failed to create kernel");
3540
3541 let result = kernel.execute("echo hello &").await.expect("execution failed");
3543 assert!(result.ok(), "background command should succeed: {}", result.err);
3544 assert!(result.out.contains("[1]"), "should return job ID: {}", result.out);
3545
3546 tokio::time::sleep(Duration::from_millis(100)).await;
3548
3549 let status = kernel.execute("cat /v/jobs/1/status").await.expect("status check failed");
3551 assert!(status.ok(), "status should succeed: {}", status.err);
3552 assert!(
3553 status.out.contains("done:") || status.out.contains("running"),
3554 "should have valid status: {}",
3555 status.out
3556 );
3557
3558 let stdout = kernel.execute("cat /v/jobs/1/stdout").await.expect("stdout check failed");
3560 assert!(stdout.ok());
3561 assert!(stdout.out.contains("hello"));
3562 }
3563
3564}