github.com/NeowayLabs/nash@v0.2.2-0.20200127205349-a227041ffd50/internal/sh/shell.go (about) 1 package sh 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "net" 9 "os" 10 "os/signal" 11 "path/filepath" 12 "runtime" 13 "strconv" 14 "strings" 15 "sync" 16 "syscall" 17 18 "github.com/madlambda/nash/ast" 19 "github.com/madlambda/nash/errors" 20 "github.com/madlambda/nash/internal/sh/builtin" 21 "github.com/madlambda/nash/parser" 22 "github.com/madlambda/nash/sh" 23 "github.com/madlambda/nash/token" 24 ) 25 26 const ( 27 logNS = "nashell.Shell" 28 defPrompt = "\033[31mλ>\033[0m " 29 ) 30 31 type ( 32 // Env is the environment map of lists 33 Env map[string]sh.Obj 34 Var Env 35 Fns map[string]sh.FnDef 36 37 StatusCode uint8 38 39 // Shell is the core data structure. 40 Shell struct { 41 name string 42 debug bool 43 interactive bool 44 abortOnErr bool 45 logf LogFn 46 nashdPath string 47 isFn bool 48 filename string // current file being executed or imported 49 50 sigs chan os.Signal 51 interrupted bool 52 looping bool 53 54 stdin io.Reader 55 stdout io.Writer 56 stderr io.Writer 57 58 env Env 59 vars Var 60 binds Fns 61 62 root *ast.Tree 63 parent *Shell 64 65 repr string // string representation 66 67 nashpath string 68 nashroot string 69 70 *sync.Mutex 71 } 72 73 errIgnore struct { 74 *errors.NashError 75 } 76 77 errInterrupted struct { 78 *errors.NashError 79 } 80 81 errStopWalking struct { 82 *errors.NashError 83 } 84 ) 85 86 const ( 87 ESuccess StatusCode = 0 88 ENotFound = 127 89 ENotStarted = 255 90 ) 91 92 func newErrIgnore(format string, arg ...interface{}) error { 93 e := &errIgnore{ 94 NashError: errors.NewError(format, arg...), 95 } 96 97 return e 98 } 99 100 func (e *errIgnore) Ignore() bool { return true } 101 102 func newErrInterrupted(format string, arg ...interface{}) error { 103 return &errInterrupted{ 104 NashError: errors.NewError(format, arg...), 105 } 106 } 107 108 func (e *errInterrupted) Interrupted() bool { return true } 109 110 func newErrStopWalking() *errStopWalking { 111 return &errStopWalking{ 112 NashError: errors.NewError("return"), 113 } 114 } 115 116 func (e *errStopWalking) StopWalking() bool { return true } 117 118 func NewAbortShell(nashpath string, nashroot string) (*Shell, error) { 119 return newShell(nashpath, nashroot, true) 120 } 121 122 // NewShell creates a new shell object 123 // nashpath will be used to search libraries and nashroot will be used to 124 // search for the standard library shipped with the language. 125 func NewShell(nashpath string, nashroot string) (*Shell, error) { 126 return newShell(nashpath, nashroot, false) 127 } 128 129 func newShell(nashpath string, nashroot string, abort bool) (*Shell, error) { 130 shell := &Shell{ 131 name: "parent scope", 132 interactive: false, 133 abortOnErr: abort, 134 isFn: false, 135 logf: NewLog(logNS, false), 136 nashdPath: nashdAutoDiscover(), 137 stdout: os.Stdout, 138 stderr: os.Stderr, 139 stdin: os.Stdin, 140 env: make(Env), 141 vars: make(Var), 142 binds: make(Fns), 143 Mutex: &sync.Mutex{}, 144 sigs: make(chan os.Signal, 1), 145 filename: "<interactive>", 146 nashpath: nashpath, 147 nashroot: nashroot, 148 } 149 150 err := shell.setup() 151 if err != nil { 152 return nil, err 153 } 154 155 shell.setupSignals() 156 err = validateDirs(nashpath, nashroot) 157 if err != nil { 158 if shell.abortOnErr { 159 return nil, err 160 } 161 162 printerr := func(msg string) { 163 shell.Stderr().Write([]byte(msg + "\n")) 164 } 165 printerr(err.Error()) 166 printerr("please check your NASHPATH and NASHROOT so they point to valid locations") 167 } 168 169 return shell, nil 170 } 171 172 // NewSubShell creates a nashell.Shell that inherits the parent shell stdin, 173 // stdout, stderr and mutex lock. 174 // Every variable and function lookup is done first in the subshell and then, if 175 // not found, in the parent shell recursively. 176 func NewSubShell(name string, parent *Shell) *Shell { 177 return &Shell{ 178 name: name, 179 isFn: true, 180 parent: parent, 181 logf: NewLog(logNS, false), 182 nashdPath: nashdAutoDiscover(), 183 stdout: parent.Stdout(), 184 stderr: parent.Stderr(), 185 stdin: parent.Stdin(), 186 env: make(Env), 187 vars: make(Var), 188 binds: make(Fns), 189 Mutex: parent.Mutex, 190 filename: parent.filename, 191 } 192 } 193 194 func (shell *Shell) NashPath() string { 195 return shell.nashpath 196 } 197 198 // initEnv creates a new environment from old one 199 func (shell *Shell) initEnv(processEnv []string) error { 200 largs := make([]sh.Obj, len(os.Args)) 201 202 for i := 0; i < len(os.Args); i++ { 203 largs[i] = sh.NewStrObj(os.Args[i]) 204 } 205 206 argv := sh.NewListObj(largs) 207 208 shell.Setenv("argv", argv) 209 shell.Newvar("argv", argv) 210 211 for _, penv := range processEnv { 212 var value sh.Obj 213 p := strings.Split(penv, "=") 214 215 if len(p) >= 2 { 216 // TODO(i4k): handle lists correctly in the future 217 // argv is not special, every list must be handled correctly 218 if p[0] == "argv" { 219 continue 220 } 221 222 value = sh.NewStrObj(strings.Join(p[1:], "=")) 223 224 shell.Setenv(p[0], value) 225 shell.Newvar(p[0], value) 226 } 227 } 228 229 pidVal := sh.NewStrObj(strconv.Itoa(os.Getpid())) 230 231 shell.Setenv("PID", pidVal) 232 shell.Newvar("PID", pidVal) 233 234 if _, ok := shell.Getenv("SHELL"); !ok { 235 shellVal := sh.NewStrObj(nashdAutoDiscover()) 236 shell.Setenv("SHELL", shellVal) 237 shell.Newvar("SHELL", shellVal) 238 } 239 240 cwd, err := os.Getwd() 241 242 if err != nil { 243 return err 244 } 245 246 cwdObj := sh.NewStrObj(cwd) 247 shell.Setenv("PWD", cwdObj) 248 shell.Newvar("PWD", cwdObj) 249 250 return nil 251 } 252 253 // Reset internal state 254 func (shell *Shell) Reset() { 255 shell.vars = make(Var) 256 shell.env = make(Env) 257 shell.binds = make(Fns) 258 } 259 260 // SetDebug enable/disable debug in the shell 261 func (shell *Shell) SetDebug(d bool) { 262 shell.debug = d 263 shell.logf = NewLog(logNS, d) 264 } 265 266 // SetInteractive enable/disable shell interactive mode 267 func (shell *Shell) SetInteractive(i bool) { 268 shell.interactive = i 269 270 if i { 271 _ = shell.setupDefaultBindings() 272 } 273 } 274 275 func (shell *Shell) Interactive() bool { 276 if shell.parent != nil { 277 return shell.parent.Interactive() 278 } 279 280 return shell.interactive 281 } 282 283 func (shell *Shell) SetName(a string) { 284 shell.name = a 285 } 286 287 func (shell *Shell) Name() string { return shell.name } 288 289 func (shell *Shell) SetParent(a *Shell) { 290 shell.parent = a 291 } 292 293 func (shell *Shell) Environ() Env { 294 if shell.parent != nil { 295 return shell.parent.Environ() 296 } 297 298 return shell.env 299 } 300 301 func (shell *Shell) Getenv(name string) (sh.Obj, bool) { 302 if shell.parent != nil { 303 return shell.parent.Getenv(name) 304 } 305 306 value, ok := shell.env[name] 307 return value, ok 308 } 309 310 func (shell *Shell) Setenv(name string, value sh.Obj) { 311 if shell.parent != nil { 312 shell.parent.Setenv(name, value) 313 return 314 } 315 316 shell.Newvar(name, value) 317 318 shell.env[name] = value 319 os.Setenv(name, value.String()) 320 } 321 322 func (shell *Shell) SetEnviron(processEnv []string) { 323 shell.env = make(Env) 324 325 for _, penv := range processEnv { 326 var value sh.Obj 327 p := strings.Split(penv, "=") 328 329 if len(p) == 2 { 330 value = sh.NewStrObj(p[1]) 331 332 shell.Setenv(p[0], value) 333 shell.Newvar(p[0], value) 334 } 335 } 336 } 337 338 // GetLocalvar returns a local scoped variable. 339 func (shell *Shell) GetLocalvar(name string) (sh.Obj, bool) { 340 v, ok := shell.vars[name] 341 return v, ok 342 } 343 344 // Getvar returns the value of the variable name. It could look in their 345 // parent scopes if not found locally. 346 func (shell *Shell) Getvar(name string) (sh.Obj, bool) { 347 if value, ok := shell.vars[name]; ok { 348 return value, ok 349 } 350 351 if shell.parent != nil { 352 return shell.parent.Getvar(name) 353 } 354 355 return nil, false 356 } 357 358 // GetFn returns the function name or error if not found. 359 func (shell *Shell) GetFn(name string) (*sh.FnObj, error) { 360 shell.logf("Looking for function '%s' on shell '%s'\n", name, shell.name) 361 if obj, ok := shell.vars[name]; ok { 362 if obj.Type() == sh.FnType { 363 fnObj := obj.(*sh.FnObj) 364 return fnObj, nil 365 } 366 return nil, errors.NewError("Identifier '%s' is not a function", name) 367 } 368 369 if shell.parent != nil { 370 return shell.parent.GetFn(name) 371 } 372 373 return nil, fmt.Errorf("function '%s' not found", name) 374 } 375 376 func (shell *Shell) Setbindfn(name string, value sh.FnDef) { 377 shell.binds[name] = value 378 } 379 380 func (shell *Shell) Getbindfn(cmdName string) (sh.FnDef, bool) { 381 if fn, ok := shell.binds[cmdName]; ok { 382 return fn, true 383 } 384 385 if shell.parent != nil { 386 return shell.parent.Getbindfn(cmdName) 387 } 388 389 return nil, false 390 } 391 392 // Newvar creates a new variable in the scope. 393 func (shell *Shell) Newvar(name string, value sh.Obj) { 394 shell.vars[name] = value 395 } 396 397 // Setvar updates the value of an existing variable. If the variable 398 // doesn't exist in current scope it looks up in their parent scopes. 399 // It returns true if the variable was found and updated. 400 func (shell *Shell) Setvar(name string, value sh.Obj) bool { 401 _, ok := shell.vars[name] 402 if ok { 403 shell.vars[name] = value 404 return true 405 } 406 407 if shell.parent != nil { 408 return shell.parent.Setvar(name, value) 409 } 410 411 return false 412 } 413 414 func (shell *Shell) IsFn() bool { return shell.isFn } 415 func (shell *Shell) SetIsFn(b bool) { shell.isFn = b } 416 417 // SetNashdPath sets an alternativa path to nashd 418 func (shell *Shell) SetNashdPath(path string) { 419 shell.nashdPath = path 420 } 421 422 // SetStdin sets the stdin for commands 423 func (shell *Shell) SetStdin(in io.Reader) { 424 shell.stdin = in 425 } 426 427 // SetStdout sets stdout for commands 428 func (shell *Shell) SetStdout(out io.Writer) { 429 shell.stdout = out 430 } 431 432 // SetStderr sets stderr for commands 433 func (shell *Shell) SetStderr(err io.Writer) { 434 shell.stderr = err 435 } 436 437 func (shell *Shell) Stdout() io.Writer { return shell.stdout } 438 func (shell *Shell) Stderr() io.Writer { return shell.stderr } 439 func (shell *Shell) Stdin() io.Reader { return shell.stdin } 440 441 // SetTree sets the internal tree of the interpreter. It's used for 442 // sub-shells like `fn`. 443 func (shell *Shell) SetTree(t *ast.Tree) { 444 shell.root = t 445 } 446 447 // Tree returns the internal tree of the subshell. 448 func (shell *Shell) Tree() *ast.Tree { return shell.root } 449 450 // SetRepr set the string representation of the shell 451 func (shell *Shell) SetRepr(a string) { 452 shell.repr = a 453 } 454 455 func (shell *Shell) setupBuiltin() { 456 for name, constructor := range builtin.Constructors() { 457 fnDef := newBuiltinFnDef(name, shell, constructor) 458 shell.Newvar(name, sh.NewFnObj(fnDef)) 459 } 460 } 461 462 func (shell *Shell) setupDefaultBindings() error { 463 // only one builtin fn... no need for advanced machinery yet 464 homeEnvVar := "HOME" 465 if runtime.GOOS == "windows" { 466 homeEnvVar = "HOMEPATH" 467 } 468 err := shell.Exec(shell.name, fmt.Sprintf(`fn nash_builtin_cd(args...) { 469 var path = "" 470 var l <= len($args) 471 if $l == "0" { 472 path = $%s 473 } else { 474 path = $args[0] 475 } 476 477 chdir($path) 478 } 479 480 bindfn nash_builtin_cd cd`, homeEnvVar)) 481 482 return err 483 } 484 485 func (shell *Shell) setup() error { 486 err := shell.initEnv(os.Environ()) 487 if err != nil { 488 return err 489 } 490 491 if shell.env["PROMPT"] == nil { 492 pobj := sh.NewStrObj(defPrompt) 493 shell.Setenv("PROMPT", pobj) 494 shell.Newvar("PROMPT", pobj) 495 } 496 497 _, ok := shell.Getvar("_") 498 if !ok { 499 shell.Newvar("_", sh.NewStrObj("")) 500 } 501 502 shell.setupBuiltin() 503 return err 504 } 505 506 func (shell *Shell) setupSignals() { 507 signal.Notify(shell.sigs, syscall.SIGINT) 508 509 go func() { 510 for { 511 sig := <-shell.sigs 512 513 switch sig { 514 case syscall.SIGINT: 515 shell.Lock() 516 517 // TODO(i4k): Review implementation when interrupted inside 518 // function loops 519 if shell.looping { 520 shell.setIntr(true) 521 } 522 523 shell.Unlock() 524 default: 525 fmt.Printf("%s\n", sig) 526 } 527 } 528 }() 529 } 530 531 // TriggerCTRLC mock the user pressing CTRL-C in the terminal 532 func (shell *Shell) TriggerCTRLC() error { 533 p, err := os.FindProcess(os.Getpid()) 534 if err != nil { 535 return err 536 } 537 538 return p.Signal(syscall.SIGINT) 539 } 540 541 // setIntr *do not lock*. You must do it yourself! 542 func (shell *Shell) setIntr(b bool) { 543 if shell.parent != nil { 544 shell.parent.setIntr(b) 545 return 546 } 547 548 shell.interrupted = b 549 } 550 551 // getIntr returns true if nash was interrupted by CTRL-C 552 func (shell *Shell) getIntr() bool { 553 if shell.parent != nil { 554 return shell.parent.getIntr() 555 } 556 557 return shell.interrupted 558 } 559 560 // Exec executes the commands specified by string content 561 func (shell *Shell) Exec(path, content string) error { 562 p := parser.NewParser(path, content) 563 564 tr, err := p.Parse() 565 566 if err != nil { 567 return err 568 } 569 570 _, err = shell.ExecuteTree(tr) 571 return err 572 } 573 574 // Execute the nash file at given path 575 func (shell *Shell) ExecFile(path string) error { 576 bkCurFile := shell.filename 577 578 content, err := ioutil.ReadFile(path) 579 580 if err != nil { 581 return err 582 } 583 584 shell.filename = path 585 586 defer func() { 587 shell.filename = bkCurFile 588 }() 589 590 return shell.Exec(path, string(content)) 591 } 592 593 func (shell *Shell) newvar(name *ast.NameNode, value sh.Obj) error { 594 if name.Index == nil { 595 shell.Newvar(name.Ident, value) 596 return nil 597 } 598 599 // handles ident[x] = v 600 601 obj, ok := shell.Getvar(name.Ident) 602 if !ok { 603 return errors.NewEvalError(shell.filename, 604 name, "Variable %s not found", name.Ident) 605 } 606 607 index, err := shell.evalIndex(name.Index) 608 if err != nil { 609 return err 610 } 611 612 col, err := sh.NewWriteableCollection(obj) 613 if err != nil { 614 return errors.NewEvalError(shell.filename, name, err.Error()) 615 } 616 617 err = col.Set(index, value) 618 if err != nil { 619 return errors.NewEvalError( 620 shell.filename, 621 name, 622 "error[%s] setting var", 623 err, 624 ) 625 } 626 627 shell.Newvar(name.Ident, obj) 628 return nil 629 } 630 631 func (shell *Shell) setvar(name *ast.NameNode, value sh.Obj) error { 632 if name.Index == nil { 633 if !shell.Setvar(name.Ident, value) { 634 return errors.NewEvalError(shell.filename, 635 name, "Variable '%s' is not initialized. Use 'var %s = <value>'", 636 name, name) 637 } 638 return nil 639 } 640 641 obj, ok := shell.Getvar(name.Ident) 642 if !ok { 643 return errors.NewEvalError(shell.filename, 644 name, "Variable %s not found", name.Ident) 645 } 646 647 index, err := shell.evalIndex(name.Index) 648 if err != nil { 649 return err 650 } 651 652 col, err := sh.NewWriteableCollection(obj) 653 if err != nil { 654 return errors.NewEvalError(shell.filename, name, err.Error()) 655 } 656 657 err = col.Set(index, value) 658 if err != nil { 659 return errors.NewEvalError( 660 shell.filename, 661 name, 662 "error[%s] setting var", 663 err, 664 ) 665 } 666 667 if !shell.Setvar(name.Ident, obj) { 668 return errors.NewEvalError(shell.filename, 669 name, "Variable '%s' is not initialized. Use 'var %s = <value>'", 670 name, name) 671 } 672 return nil 673 } 674 675 func (shell *Shell) setvars(names []*ast.NameNode, values []sh.Obj) error { 676 for i := 0; i < len(names); i++ { 677 err := shell.setvar(names[i], values[i]) 678 if err != nil { 679 return err 680 } 681 } 682 return nil 683 } 684 685 func (shell *Shell) newvars(names []*ast.NameNode, values []sh.Obj) { 686 for i := 0; i < len(names); i++ { 687 shell.newvar(names[i], values[i]) 688 } 689 } 690 691 func (shell *Shell) setcmdvars(names []*ast.NameNode, stdout, stderr, status sh.Obj) error { 692 if len(names) == 3 { 693 err := shell.setvar(names[0], stdout) 694 if err != nil { 695 return err 696 } 697 err = shell.setvar(names[1], stderr) 698 if err != nil { 699 return err 700 } 701 return shell.setvar(names[2], status) 702 } else if len(names) == 2 { 703 err := shell.setvar(names[0], stdout) 704 if err != nil { 705 return err 706 } 707 return shell.setvar(names[1], status) 708 } else if len(names) == 1 { 709 return shell.setvar(names[0], stdout) 710 } 711 712 panic(fmt.Sprintf("internal error: expects 1 <= len(names) <= 3,"+ 713 " but got %d", 714 len(names))) 715 716 return nil 717 } 718 719 func (shell *Shell) newcmdvars(names []*ast.NameNode, stdout, stderr, status sh.Obj) { 720 if len(names) == 3 { 721 shell.newvar(names[0], stdout) 722 shell.newvar(names[1], stderr) 723 shell.newvar(names[2], status) 724 } else if len(names) == 2 { 725 shell.newvar(names[0], stdout) 726 shell.newvar(names[1], status) 727 } else if len(names) == 1 { 728 shell.newvar(names[0], stdout) 729 } else { 730 panic(fmt.Sprintf("internal error: expects 1 <= len(names) <= 3,"+ 731 " but got %d", 732 len(names))) 733 } 734 } 735 736 // evalConcat reveives the AST representation of a concatenation of objects and 737 // returns the string representation, or error. 738 func (shell *Shell) evalConcat(path ast.Expr) (string, error) { 739 var pathStr string 740 741 if path.Type() != ast.NodeConcatExpr { 742 return "", fmt.Errorf("Invalid node %+v", path) 743 } 744 745 concatExpr := path.(*ast.ConcatExpr) 746 concat := concatExpr.List() 747 748 for i := 0; i < len(concat); i++ { 749 part := concat[i] 750 751 switch part.Type() { 752 case ast.NodeConcatExpr: 753 return "", errors.NewEvalError(shell.filename, part, 754 "Nested concat is not allowed: %s", part) 755 case ast.NodeVarExpr, ast.NodeIndexExpr: 756 partValue, err := shell.evalVariable(part) 757 if err != nil { 758 return "", err 759 } 760 761 if partValue.Type() == sh.ListType { 762 return "", errors.NewEvalError(shell.filename, 763 part, "Concat of list variables is not allowed: %v = %v", 764 part, partValue) 765 } else if partValue.Type() != sh.StringType { 766 return "", errors.NewEvalError(shell.filename, part, 767 "Invalid concat element: %v", partValue) 768 } 769 770 strval := partValue.(*sh.StrObj) 771 pathStr += strval.Str() 772 case ast.NodeStringExpr: 773 str, ok := part.(*ast.StringExpr) 774 if !ok { 775 return "", errors.NewEvalError(shell.filename, part, 776 "Failed to eval string: %s", part) 777 } 778 779 pathStr += str.Value() 780 case ast.NodeFnInv: 781 fnNode := part.(*ast.FnInvNode) 782 result, err := shell.executeFnInv(fnNode) 783 if err != nil { 784 return "", err 785 } 786 787 if len(result) == 0 || len(result) > 1 { 788 return "", errors.NewEvalError(shell.filename, part, 789 "Function '%s' used in string concat but returns %d values.", 790 fnNode.Name) 791 } 792 obj := result[0] 793 if obj.Type() != sh.StringType { 794 return "", errors.NewEvalError(shell.filename, part, 795 "Function '%s' used in concat but returns a '%s'", obj.Type()) 796 } 797 798 str := obj.(*sh.StrObj) 799 pathStr += str.Str() 800 case ast.NodeListExpr: 801 return "", errors.NewEvalError(shell.filename, part, 802 "Concat of lists is not allowed: %+v", part.String()) 803 default: 804 return "", errors.NewEvalError(shell.filename, part, 805 "Invalid argument: %+v", part) 806 } 807 } 808 809 return pathStr, nil 810 } 811 812 func (shell *Shell) executeNode(node ast.Node) ([]sh.Obj, error) { 813 var ( 814 objs []sh.Obj 815 err error 816 ) 817 818 shell.logf("Executing node: %v\n", node) 819 820 switch node.Type() { 821 case ast.NodeImport: 822 err = shell.executeImport(node.(*ast.ImportNode)) 823 case ast.NodeComment: 824 // ignore 825 case ast.NodeSetenv: 826 err = shell.executeSetenv(node.(*ast.SetenvNode)) 827 case ast.NodeVarAssignDecl: 828 err = shell.executeVarAssign(node.(*ast.VarAssignDeclNode)) 829 case ast.NodeVarExecAssignDecl: 830 err = shell.executeVarExecAssign(node.(*ast.VarExecAssignDeclNode)) 831 case ast.NodeAssign: 832 err = shell.executeAssignment(node.(*ast.AssignNode)) 833 case ast.NodeExecAssign: 834 err = shell.executeExecAssign(node.(*ast.ExecAssignNode)) 835 case ast.NodeCommand: 836 _, err = shell.executeCommand(node.(*ast.CommandNode)) 837 case ast.NodePipe: 838 _, err = shell.executePipe(node.(*ast.PipeNode)) 839 case ast.NodeRfork: 840 err = shell.executeRfork(node.(*ast.RforkNode)) 841 case ast.NodeIf: 842 objs, err = shell.executeIf(node.(*ast.IfNode)) 843 case ast.NodeFnDecl: 844 err = shell.executeFnDecl(node.(*ast.FnDeclNode)) 845 case ast.NodeFnInv: 846 // invocation ignoring output 847 _, err = shell.executeFnInv(node.(*ast.FnInvNode)) 848 case ast.NodeFor: 849 objs, err = shell.executeFor(node.(*ast.ForNode)) 850 case ast.NodeBindFn: 851 err = shell.executeBindFn(node.(*ast.BindFnNode)) 852 case ast.NodeReturn: 853 if shell.IsFn() { 854 objs, err = shell.executeReturn(node.(*ast.ReturnNode)) 855 } else { 856 err = errors.NewEvalError(shell.filename, 857 node, 858 "Unexpected return outside of function declaration.") 859 } 860 default: 861 // should never get here 862 return nil, errors.NewEvalError(shell.filename, node, 863 "invalid node: %v.", node.Type()) 864 } 865 866 return objs, err 867 } 868 869 func (shell *Shell) ExecuteTree(tr *ast.Tree) ([]sh.Obj, error) { 870 return shell.executeTree(tr, true) 871 } 872 873 // executeTree evaluates the given tree 874 func (shell *Shell) executeTree(tr *ast.Tree, stopable bool) ([]sh.Obj, error) { 875 if tr == nil || tr.Root == nil { 876 return nil, errors.NewError("empty abstract syntax tree to execute") 877 } 878 879 root := tr.Root 880 881 for _, node := range root.Nodes { 882 objs, err := shell.executeNode(node) 883 if err != nil { 884 type ( 885 IgnoreError interface { 886 Ignore() bool 887 } 888 889 InterruptedError interface { 890 Interrupted() bool 891 } 892 893 StopWalkingError interface { 894 StopWalking() bool 895 } 896 ) 897 898 if errIgnore, ok := err.(IgnoreError); ok && errIgnore.Ignore() { 899 continue 900 } 901 902 if errInterrupted, ok := err.(InterruptedError); ok && errInterrupted.Interrupted() { 903 return nil, err 904 } 905 906 if errStopWalking, ok := err.(StopWalkingError); stopable && ok && errStopWalking.StopWalking() { 907 return objs, nil 908 } 909 910 return objs, err 911 } 912 } 913 914 return nil, nil 915 } 916 917 func (shell *Shell) executeReturn(n *ast.ReturnNode) ([]sh.Obj, error) { 918 var returns []sh.Obj 919 920 returnExprs := n.Returns 921 922 for i := 0; i < len(returnExprs); i++ { 923 retExpr := returnExprs[i] 924 925 obj, err := shell.evalExpr(retExpr) 926 if err != nil { 927 return nil, err 928 } 929 930 returns = append(returns, obj) 931 } 932 933 return returns, newErrStopWalking() 934 } 935 936 func (shell *Shell) getNashRootFromGOPATH(preverr error) (string, error) { 937 g, hasgopath := shell.Getenv("GOPATH") 938 if !hasgopath { 939 return "", errors.NewError("%s\nno GOPATH env var setted", preverr) 940 } 941 gopath := g.String() 942 return filepath.Join(gopath, filepath.FromSlash("/src/github.com/madlambda/nash")), nil 943 } 944 945 func isValidNashRoot(nashroot string) bool { 946 _, err := os.Stat(filepath.Join(nashroot, "stdlib")) 947 return err == nil 948 } 949 950 func (shell *Shell) executeImport(node *ast.ImportNode) error { 951 obj, err := shell.evalExpr(node.Path) 952 if err != nil { 953 return errors.NewEvalError(shell.filename, 954 node, err.Error()) 955 } 956 957 if obj.Type() != sh.StringType { 958 return errors.NewEvalError(shell.filename, 959 node.Path, 960 "Invalid type on import argument: %s", obj.Type()) 961 } 962 963 objstr := obj.(*sh.StrObj) 964 fname := objstr.Str() 965 966 shell.logf("Importing '%s'", fname) 967 968 var ( 969 tries []string 970 hasExt bool 971 ) 972 973 hasExt = filepath.Ext(fname) != "" 974 if filepath.IsAbs(fname) { 975 tries = append(tries, fname) 976 977 if !hasExt { 978 tries = append(tries, fname+".sh") 979 } 980 } 981 982 if shell.filename != "" { 983 localFile := filepath.Join(filepath.Dir(shell.filename), fname) 984 tries = append(tries, localFile) 985 986 if !hasExt { 987 tries = append(tries, localFile+".sh") 988 } 989 } 990 991 tries = append(tries, filepath.Join(shell.nashpath, "lib", fname)) 992 if !hasExt { 993 tries = append(tries, filepath.Join(shell.nashpath, "lib", fname+".sh")) 994 } 995 996 tries = append(tries, filepath.Join(shell.nashroot, "stdlib", fname+".sh")) 997 998 shell.logf("Trying %q\n", tries) 999 1000 for _, path := range tries { 1001 d, err := os.Stat(path) 1002 1003 if err != nil { 1004 continue 1005 } 1006 1007 if m := d.Mode(); !m.IsDir() { 1008 return shell.ExecFile(path) 1009 } 1010 } 1011 1012 errmsg := fmt.Sprintf( 1013 "Failed to import path '%s'. The locations below have been tried:\n \"%s\"", 1014 fname, 1015 strings.Join(tries, `", "`), 1016 ) 1017 1018 return errors.NewEvalError(shell.filename, node, errmsg) 1019 } 1020 1021 // executePipe executes a pipe of ast.Command's. Each command can be 1022 // a path command in the operating system or a function bind to a 1023 // command name. 1024 // The error of each command can be suppressed prepending it with '-' (dash). 1025 // The error returned will be a string representing the errors (or none) of 1026 // each command separated by '|'. The $status of pipe execution will be 1027 // the $status of each command separated by '|'. 1028 func (shell *Shell) executePipe(pipe *ast.PipeNode) (sh.Obj, error) { 1029 var ( 1030 closeFiles []io.Closer 1031 closeAfterWait []io.Closer 1032 errIndex int 1033 err error 1034 ) 1035 1036 defer func() { 1037 for _, c := range closeAfterWait { 1038 c.Close() 1039 } 1040 }() 1041 1042 nodeCommands := pipe.Commands() 1043 1044 if len(nodeCommands) < 2 { 1045 return sh.NewStrObj(strconv.Itoa(ENotStarted)), 1046 errors.NewEvalError(shell.filename, 1047 pipe, "Pipe requires at least two commands.") 1048 } 1049 1050 cmds := make([]sh.Runner, len(nodeCommands)) 1051 errs := make([]string, len(nodeCommands)) 1052 igns := make([]bool, len(nodeCommands)) // ignoreErrors 1053 cods := make([]string, len(nodeCommands)) 1054 1055 for i := 0; i < len(nodeCommands); i++ { 1056 errs[i] = "not started" 1057 cods[i] = strconv.Itoa(ENotStarted) 1058 } 1059 1060 last := len(nodeCommands) - 1 1061 1062 envVars := buildenv(shell.Environ()) 1063 1064 // Create all commands 1065 for i := 0; i < len(nodeCommands); i++ { 1066 var ( 1067 cmd sh.Runner 1068 ignore bool 1069 args []sh.Obj 1070 ) 1071 1072 nodeCmd := nodeCommands[i] 1073 1074 cmd, ignore, err = shell.getCommand(nodeCmd) 1075 1076 igns[i] = ignore 1077 1078 if err != nil { 1079 errIndex = i 1080 cods[i] = strconv.Itoa(ENotFound) 1081 goto pipeError 1082 } 1083 1084 // SetEnviron must be called before SetArgs 1085 // otherwise the subshell will have the arguments 1086 // shadowed by parent env 1087 cmd.SetEnviron(envVars) 1088 args, err = shell.evalExprs(nodeCmd.Args()) 1089 1090 if err != nil { 1091 errIndex = i 1092 goto pipeError 1093 } 1094 1095 err = cmd.SetArgs(args) 1096 if err != nil { 1097 errIndex = i 1098 goto pipeError 1099 } 1100 1101 cmd.SetStdin(shell.stdin) 1102 1103 if i < last { 1104 closeFiles, err = shell.setRedirects(cmd, nodeCmd.Redirects()) 1105 closeAfterWait = append(closeAfterWait, closeFiles...) 1106 1107 if err != nil { 1108 errIndex = i 1109 goto pipeError 1110 } 1111 } 1112 1113 cmds[i] = cmd 1114 } 1115 1116 // Shell does not support stdin redirection yet 1117 cmds[0].SetStdin(shell.stdin) 1118 1119 // Setup the commands. Pointing the stdin of next command to stdout of previous. 1120 // Except the stdout of last one 1121 for i, cmd := range cmds[:last] { 1122 var ( 1123 stdin io.ReadCloser 1124 ) 1125 1126 // StdoutPipe complains if Stdout is already set 1127 cmd.SetStdout(nil) 1128 stdin, err = cmd.StdoutPipe() 1129 1130 if err != nil { 1131 errIndex = i 1132 goto pipeError 1133 } 1134 1135 cmds[i+1].SetStdin(stdin) 1136 } 1137 1138 cmds[last].SetStdout(shell.stdout) 1139 cmds[last].SetStderr(shell.stderr) 1140 1141 closeFiles, err = shell.setRedirects(cmds[last], nodeCommands[last].Redirects()) 1142 closeAfterWait = append(closeAfterWait, closeFiles...) 1143 1144 if err != nil { 1145 errIndex = last 1146 goto pipeError 1147 } 1148 1149 for i := 0; i < len(cmds); i++ { 1150 cmd := cmds[i] 1151 1152 err = cmd.Start() 1153 1154 if err != nil { 1155 errIndex = i 1156 goto pipeError 1157 } 1158 1159 errs[i] = "success" 1160 cods[i] = "0" 1161 } 1162 1163 for i, cmd := range cmds { 1164 err = cmd.Wait() 1165 1166 if err != nil { 1167 errIndex = i 1168 goto pipeError 1169 } 1170 1171 errs[i] = "success" 1172 cods[i] = "0" 1173 } 1174 1175 return sh.NewStrObj("0"), nil 1176 1177 pipeError: 1178 if igns[errIndex] { 1179 errs[errIndex] = "none" 1180 } else { 1181 errs[errIndex] = err.Error() 1182 } 1183 1184 cods[errIndex] = getErrStatus(err, cods[errIndex]) 1185 1186 err = errors.NewEvalError(shell.filename, 1187 pipe, strings.Join(errs, "|")) 1188 1189 // verify if all status codes are the same 1190 uniqCodes := make(map[string]struct{}) 1191 var uniqCode string 1192 1193 for i := 0; i < len(cods); i++ { 1194 uniqCodes[cods[i]] = struct{}{} 1195 uniqCode = cods[i] 1196 } 1197 1198 var status sh.Obj 1199 1200 if len(uniqCodes) == 1 { 1201 // if all status are the same 1202 status = sh.NewStrObj(uniqCode) 1203 } else { 1204 status = sh.NewStrObj(strings.Join(cods, "|")) 1205 } 1206 1207 if igns[errIndex] { 1208 return status, nil 1209 } 1210 1211 return status, err 1212 } 1213 1214 func (shell *Shell) openRedirectLocation(location ast.Expr) (io.WriteCloser, error) { 1215 var protocol string 1216 1217 locationObj, err := shell.evalExpr(location) 1218 if err != nil { 1219 return nil, err 1220 } 1221 1222 if locationObj.Type() != sh.StringType { 1223 return nil, errors.NewEvalError(shell.filename, 1224 location, 1225 "Redirection to invalid object type: %v (%s)", locationObj, locationObj.Type()) 1226 } 1227 1228 objstr := locationObj.(*sh.StrObj) 1229 locationStr := objstr.Str() 1230 1231 if len(locationStr) > 6 { 1232 if locationStr[0:6] == "tcp://" { 1233 protocol = "tcp" 1234 } else if locationStr[0:6] == "udp://" { 1235 protocol = "udp" 1236 } else if len(locationStr) > 7 && locationStr[0:7] == "unix://" { 1237 protocol = "unix" 1238 } 1239 } 1240 1241 if protocol == "" { 1242 return os.OpenFile(locationStr, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) 1243 } 1244 1245 switch protocol { 1246 case "tcp", "udp": 1247 netParts := strings.Split(locationStr[6:], ":") 1248 1249 if len(netParts) != 2 { 1250 return nil, errors.NewEvalError(shell.filename, 1251 location, 1252 "Invalid tcp/udp address: %s", locationStr) 1253 } 1254 1255 url := netParts[0] + ":" + netParts[1] 1256 1257 return net.Dial(protocol, url) 1258 case "unix": 1259 return net.Dial(protocol, locationStr[7:]) 1260 } 1261 1262 return nil, errors.NewEvalError(shell.filename, location, 1263 "Unexpected redirection value: %s", locationStr) 1264 } 1265 1266 func (shell *Shell) setRedirects(cmd sh.Runner, redirDecls []*ast.RedirectNode) ([]io.Closer, error) { 1267 var closeAfterWait []io.Closer 1268 1269 for _, r := range redirDecls { 1270 closeFiles, err := shell.buildRedirect(cmd, r) 1271 closeAfterWait = append(closeAfterWait, closeFiles...) 1272 1273 if err != nil { 1274 return closeAfterWait, err 1275 } 1276 } 1277 1278 return closeAfterWait, nil 1279 } 1280 1281 func (shell *Shell) buildRedirect(cmd sh.Runner, redirDecl *ast.RedirectNode) ([]io.Closer, error) { 1282 var closeAfterWait []io.Closer 1283 1284 if redirDecl.LeftFD() > 2 || redirDecl.LeftFD() < ast.RedirMapSupress { 1285 return closeAfterWait, errors.NewEvalError(shell.filename, 1286 redirDecl, 1287 "Invalid file descriptor redirection: fd=%d", redirDecl.LeftFD()) 1288 } 1289 1290 if redirDecl.RightFD() > 2 || redirDecl.RightFD() < ast.RedirMapSupress { 1291 return closeAfterWait, errors.NewEvalError(shell.filename, 1292 redirDecl, 1293 "Invalid file descriptor redirection: fd=%d", redirDecl.RightFD()) 1294 } 1295 1296 var err error 1297 1298 // Note(i4k): We need to remove the repetitive code in some smarter way 1299 switch redirDecl.LeftFD() { 1300 case 0: 1301 return closeAfterWait, fmt.Errorf("Does not support stdin redirection yet") 1302 case 1: 1303 switch redirDecl.RightFD() { 1304 case 0: 1305 return closeAfterWait, errors.NewEvalError(shell.filename, 1306 redirDecl, 1307 "Invalid redirect mapping: %d -> %d", 1, 0) 1308 case 1: // do nothing 1309 case 2: 1310 cmd.SetStdout(cmd.Stderr()) 1311 case ast.RedirMapNoValue: 1312 if redirDecl.Location() == nil { 1313 return closeAfterWait, errors.NewEvalError(shell.filename, 1314 redirDecl, 1315 "Missing file in redirection: >[%d] <??>", redirDecl.LeftFD()) 1316 } 1317 1318 file, err := shell.openRedirectLocation(redirDecl.Location()) 1319 if err != nil { 1320 return closeAfterWait, err 1321 } 1322 1323 cmd.SetStdout(file) 1324 closeAfterWait = append(closeAfterWait, file) 1325 case ast.RedirMapSupress: 1326 file := ioutil.Discard 1327 cmd.SetStdout(file) 1328 } 1329 case 2: 1330 switch redirDecl.RightFD() { 1331 case 0: 1332 return closeAfterWait, errors.NewEvalError(shell.filename, 1333 redirDecl, "Invalid redirect mapping: %d -> %d", 2, 1) 1334 case 1: 1335 cmd.SetStderr(cmd.Stdout()) 1336 case 2: // do nothing 1337 case ast.RedirMapNoValue: 1338 if redirDecl.Location() == nil { 1339 return closeAfterWait, errors.NewEvalError(shell.filename, 1340 redirDecl, 1341 "Missing file in redirection: >[%d] <??>", redirDecl.LeftFD()) 1342 } 1343 1344 file, err := shell.openRedirectLocation(redirDecl.Location()) 1345 if err != nil { 1346 return closeAfterWait, err 1347 } 1348 1349 cmd.SetStderr(file) 1350 closeAfterWait = append(closeAfterWait, file) 1351 case ast.RedirMapSupress: 1352 cmd.SetStderr(ioutil.Discard) 1353 } 1354 case ast.RedirMapNoValue: 1355 if redirDecl.Location() == nil { 1356 return closeAfterWait, errors.NewEvalError(shell.filename, 1357 redirDecl, "Missing file in redirection: >[%d] <??>", redirDecl.LeftFD()) 1358 } 1359 1360 file, err := shell.openRedirectLocation(redirDecl.Location()) 1361 if err != nil { 1362 return closeAfterWait, err 1363 } 1364 1365 cmd.SetStdout(file) 1366 closeAfterWait = append(closeAfterWait, file) 1367 } 1368 1369 return closeAfterWait, err 1370 } 1371 1372 func (shell *Shell) newBindfnRunner( 1373 c *ast.CommandNode, 1374 cmdName string, 1375 fnDef sh.FnDef, 1376 ) (sh.Runner, error) { 1377 shell.logf("Executing bind %s", cmdName) 1378 shell.logf("%s bind to %s", cmdName, fnDef.Name()) 1379 1380 if !shell.Interactive() { 1381 err := errors.NewEvalError(shell.filename, 1382 c, "'%s' is a bind to '%s'. "+ 1383 "No binds allowed in non-interactive mode.", 1384 cmdName, 1385 fnDef.Name()) 1386 return nil, err 1387 } 1388 1389 return fnDef.Build(), nil 1390 } 1391 1392 func (shell *Shell) getCommand(c *ast.CommandNode) (sh.Runner, bool, error) { 1393 var ( 1394 ignoreError bool 1395 cmd sh.Runner 1396 err error 1397 ) 1398 1399 cmdName := c.Name() 1400 1401 shell.logf("Executing: %s\n", c.Name()) 1402 1403 if len(cmdName) > 1 && cmdName[0] == '-' { 1404 ignoreError = true 1405 cmdName = cmdName[1:] 1406 1407 shell.logf("Ignoring error\n") 1408 } 1409 1410 if cmdName == "" { 1411 return nil, false, errors.NewEvalError(shell.filename, 1412 c, "Empty command name...") 1413 } 1414 1415 if fnDef, ok := shell.Getbindfn(cmdName); ok { 1416 runner, err := shell.newBindfnRunner(c, cmdName, fnDef) 1417 return runner, ignoreError, err 1418 } 1419 1420 cmd, err = NewCmd(cmdName) 1421 1422 if err != nil { 1423 type NotFound interface { 1424 NotFound() bool 1425 } 1426 1427 shell.logf("Command fails: %s", err.Error()) 1428 1429 if errNotFound, ok := err.(NotFound); ok && errNotFound.NotFound() { 1430 return nil, ignoreError, err 1431 } 1432 1433 return nil, ignoreError, err 1434 } 1435 1436 cmd.SetStdin(shell.stdin) 1437 cmd.SetStdout(shell.stdout) 1438 cmd.SetStderr(shell.stderr) 1439 1440 return cmd, ignoreError, nil 1441 } 1442 1443 func (shell *Shell) executeCommand(c *ast.CommandNode) (sh.Obj, error) { 1444 var ( 1445 ignoreError bool 1446 status = "127" 1447 envVars []string 1448 closeAfterWait []io.Closer 1449 cmd sh.Runner 1450 err error 1451 args []sh.Obj 1452 ) 1453 1454 defer func() { 1455 for _, c := range closeAfterWait { 1456 c.Close() 1457 } 1458 }() 1459 1460 cmd, ignoreError, err = shell.getCommand(c) 1461 if err != nil { 1462 goto cmdError 1463 } 1464 1465 // SetEnviron must be called before SetArgs 1466 // otherwise the subshell will have the arguments 1467 // shadowed by parent env 1468 envVars = buildenv(shell.Environ()) 1469 cmd.SetEnviron(envVars) 1470 1471 args, err = shell.evalExprs(c.Args()) 1472 if err != nil { 1473 goto cmdError 1474 } 1475 1476 err = cmd.SetArgs(args) 1477 if err != nil { 1478 goto cmdError 1479 } 1480 1481 closeAfterWait, err = shell.setRedirects(cmd, c.Redirects()) 1482 if err != nil { 1483 goto cmdError 1484 } 1485 1486 err = cmd.Start() 1487 if err != nil { 1488 goto cmdError 1489 } 1490 1491 err = cmd.Wait() 1492 if err != nil { 1493 goto cmdError 1494 } 1495 1496 return sh.NewStrObj("0"), nil 1497 1498 cmdError: 1499 statusObj := sh.NewStrObj(getErrStatus(err, status)) 1500 if ignoreError { 1501 return statusObj, newErrIgnore(err.Error()) 1502 } 1503 1504 return statusObj, err 1505 } 1506 1507 func (shell *Shell) evalList(argList *ast.ListExpr) (sh.Obj, error) { 1508 values := make([]sh.Obj, 0, len(argList.List)) 1509 1510 for _, arg := range argList.List { 1511 obj, err := shell.evalExpr(arg) 1512 if err != nil { 1513 return nil, err 1514 } 1515 1516 values = append(values, obj) 1517 } 1518 1519 return sh.NewListObj(values), nil 1520 } 1521 1522 func (shell *Shell) evalArgList(argList *ast.ListExpr) ([]sh.Obj, error) { 1523 values := make([]sh.Obj, 0, len(argList.List)) 1524 1525 for _, arg := range argList.List { 1526 obj, err := shell.evalExpr(arg) 1527 if err != nil { 1528 return nil, err 1529 } 1530 1531 values = append(values, obj) 1532 } 1533 1534 if argList.IsVariadic { 1535 return values, nil 1536 } 1537 1538 return []sh.Obj{sh.NewListObj(values)}, nil 1539 } 1540 1541 func (shell *Shell) evalIndex(index ast.Expr) (int, error) { 1542 if index.Type() != ast.NodeIntExpr && index.Type() != ast.NodeVarExpr && index.Type() != ast.NodeIndexExpr { 1543 return 0, errors.NewEvalError(shell.filename, 1544 index, "Invalid indexing type: %s", index.Type()) 1545 } 1546 1547 if index.Type() == ast.NodeIntExpr { 1548 idxArg := index.(*ast.IntExpr) 1549 return idxArg.Value(), nil 1550 } 1551 1552 idxObj, err := shell.evalVariable(index) 1553 if err != nil { 1554 return 0, err 1555 } 1556 1557 if idxObj.Type() != sh.StringType { 1558 return 0, errors.NewEvalError(shell.filename, 1559 index, "Invalid object type on index value: %s", idxObj.Type()) 1560 } 1561 1562 objstr := idxObj.(*sh.StrObj) 1563 indexNum, err := strconv.Atoi(objstr.Str()) 1564 1565 if err != nil { 1566 return 0, err 1567 } 1568 1569 return indexNum, nil 1570 } 1571 1572 func (shell *Shell) evalIndexedVar(indexVar *ast.IndexExpr) (sh.Obj, error) { 1573 v, err := shell.evalVariable(indexVar.Var) 1574 1575 if err != nil { 1576 return nil, err 1577 } 1578 1579 col, err := sh.NewCollection(v) 1580 if err != nil { 1581 return nil, errors.NewEvalError(shell.filename, indexVar.Var, err.Error()) 1582 } 1583 1584 indexNum, err := shell.evalIndex(indexVar.Index) 1585 if err != nil { 1586 return nil, err 1587 } 1588 1589 val, err := col.Get(indexNum) 1590 if err != nil { 1591 return nil, errors.NewEvalError(shell.filename, indexVar.Var, err.Error()) 1592 } 1593 return val, nil 1594 } 1595 1596 func (shell *Shell) evalArgIndexedVar(indexVar *ast.IndexExpr) ([]sh.Obj, error) { 1597 v, err := shell.evalVariable(indexVar.Var) 1598 if err != nil { 1599 return nil, err 1600 } 1601 1602 col, err := sh.NewCollection(v) 1603 if err != nil { 1604 return nil, errors.NewEvalError(shell.filename, indexVar.Var, err.Error()) 1605 } 1606 1607 indexNum, err := shell.evalIndex(indexVar.Index) 1608 if err != nil { 1609 return nil, err 1610 } 1611 1612 retval, err := col.Get(indexNum) 1613 if err != nil { 1614 return nil, errors.NewEvalError(shell.filename, indexVar.Var, err.Error()) 1615 } 1616 1617 if indexVar.IsVariadic { 1618 if retval.Type() != sh.ListType { 1619 return nil, errors.NewEvalError(shell.filename, 1620 indexVar, "Use of '...' on a non-list variable") 1621 } 1622 retlist := retval.(*sh.ListObj) 1623 return retlist.List(), nil 1624 } 1625 return []sh.Obj{retval}, nil 1626 } 1627 1628 func (shell *Shell) evalVariable(a ast.Expr) (sh.Obj, error) { 1629 var ( 1630 value sh.Obj 1631 ok bool 1632 ) 1633 1634 if a.Type() == ast.NodeIndexExpr { 1635 return shell.evalIndexedVar(a.(*ast.IndexExpr)) 1636 } 1637 1638 if a.Type() != ast.NodeVarExpr { 1639 return nil, errors.NewEvalError(shell.filename, 1640 a, "Invalid eval of non variable argument: %s", a) 1641 } 1642 1643 vexpr := a.(*ast.VarExpr) 1644 varName := vexpr.Name 1645 1646 if value, ok = shell.Getvar(varName[1:]); !ok { 1647 return nil, errors.NewEvalError(shell.filename, 1648 a, "Variable %s not set on shell %s", varName, shell.name) 1649 } 1650 return value, nil 1651 } 1652 1653 func (shell *Shell) evalArgVariable(a ast.Expr) ([]sh.Obj, error) { 1654 var ( 1655 value sh.Obj 1656 ok bool 1657 ) 1658 1659 if a.Type() == ast.NodeIndexExpr { 1660 return shell.evalArgIndexedVar(a.(*ast.IndexExpr)) 1661 } 1662 1663 if a.Type() != ast.NodeVarExpr { 1664 return nil, errors.NewEvalError(shell.filename, 1665 a, "Invalid eval of non variable argument: %s", a) 1666 } 1667 1668 vexpr := a.(*ast.VarExpr) 1669 if value, ok = shell.Getvar(vexpr.Name[1:]); !ok { 1670 return nil, errors.NewEvalError(shell.filename, 1671 a, "Variable %s not set on shell %s", vexpr.Name, 1672 shell.name) 1673 } 1674 1675 if vexpr.IsVariadic { 1676 if value.Type() != sh.ListType { 1677 return nil, errors.NewEvalError(shell.filename, 1678 a, "Variable expansion (%s) on a non-list object", 1679 vexpr.String()) 1680 } 1681 1682 return value.(*sh.ListObj).List(), nil 1683 } 1684 1685 return []sh.Obj{value}, nil 1686 } 1687 1688 func (shell *Shell) evalExprs(exprs []ast.Expr) ([]sh.Obj, error) { 1689 objs := make([]sh.Obj, 0, len(exprs)) 1690 1691 for _, expr := range exprs { 1692 obj, err := shell.evalExpr(expr) 1693 if err != nil { 1694 return nil, err 1695 } 1696 1697 objs = append(objs, obj) 1698 } 1699 1700 return objs, nil 1701 } 1702 1703 func (shell *Shell) evalArgExprs(exprs []ast.Expr) ([]sh.Obj, error) { 1704 ret := make([]sh.Obj, 0, len(exprs)) 1705 1706 for _, expr := range exprs { 1707 objs, err := shell.evalArgExpr(expr) 1708 if err != nil { 1709 return nil, err 1710 } 1711 1712 ret = append(ret, objs...) 1713 } 1714 1715 return ret, nil 1716 } 1717 1718 func (shell *Shell) evalArgExpr(expr ast.Expr) ([]sh.Obj, error) { 1719 switch expr.Type() { 1720 case ast.NodeStringExpr: 1721 if str, ok := expr.(*ast.StringExpr); ok { 1722 return []sh.Obj{ 1723 sh.NewStrObj(str.Value()), 1724 }, nil 1725 } 1726 case ast.NodeConcatExpr: 1727 if concat, ok := expr.(*ast.ConcatExpr); ok { 1728 argVal, err := shell.evalConcat(concat) 1729 if err != nil { 1730 return nil, err 1731 } 1732 1733 return []sh.Obj{ 1734 sh.NewStrObj(argVal), 1735 }, nil 1736 } 1737 case ast.NodeVarExpr: 1738 return shell.evalArgVariable(expr) 1739 case ast.NodeIndexExpr: 1740 if indexedVar, ok := expr.(*ast.IndexExpr); ok { 1741 return shell.evalArgIndexedVar(indexedVar) 1742 } 1743 case ast.NodeListExpr: 1744 if listExpr, ok := expr.(*ast.ListExpr); ok { 1745 return shell.evalArgList(listExpr) 1746 } 1747 case ast.NodeFnInv: 1748 if fnInv, ok := expr.(*ast.FnInvNode); ok { 1749 objs, err := shell.executeFnInv(fnInv) 1750 if err != nil { 1751 return nil, err 1752 } 1753 1754 if len(objs) == 0 { 1755 return nil, errors.NewEvalError(shell.filename, 1756 expr, 1757 "Function used in"+ 1758 " expression but do not return any value: %s", 1759 fnInv) 1760 } else if len(objs) != 1 { 1761 return nil, errors.NewEvalError(shell.filename, 1762 expr, 1763 "Function used in"+ 1764 " expression but it returns %d values: %10q", 1765 len(objs), objs) 1766 } 1767 1768 return []sh.Obj{objs[0]}, nil 1769 } 1770 } 1771 1772 return nil, errors.NewEvalError(shell.filename, 1773 expr, "Failed to eval expression: %+v", expr) 1774 } 1775 1776 func (shell *Shell) evalExpr(expr ast.Expr) (sh.Obj, error) { 1777 switch expr.Type() { 1778 case ast.NodeStringExpr: 1779 if str, ok := expr.(*ast.StringExpr); ok { 1780 return sh.NewStrObj(str.Value()), nil 1781 } 1782 case ast.NodeConcatExpr: 1783 if concat, ok := expr.(*ast.ConcatExpr); ok { 1784 argVal, err := shell.evalConcat(concat) 1785 if err != nil { 1786 return nil, err 1787 } 1788 1789 return sh.NewStrObj(argVal), nil 1790 } 1791 case ast.NodeVarExpr: 1792 return shell.evalVariable(expr) 1793 case ast.NodeIndexExpr: 1794 if indexedVar, ok := expr.(*ast.IndexExpr); ok { 1795 return shell.evalIndexedVar(indexedVar) 1796 } 1797 case ast.NodeListExpr: 1798 if listExpr, ok := expr.(*ast.ListExpr); ok { 1799 return shell.evalList(listExpr) 1800 } 1801 case ast.NodeFnInv: 1802 if fnInv, ok := expr.(*ast.FnInvNode); ok { 1803 objs, err := shell.executeFnInv(fnInv) 1804 if err != nil { 1805 return nil, err 1806 } 1807 1808 if len(objs) == 0 { 1809 return nil, errors.NewEvalError(shell.filename, 1810 expr, 1811 "Function used in"+ 1812 " expression but do not return any value: %s", 1813 fnInv) 1814 } else if len(objs) != 1 { 1815 return nil, errors.NewEvalError(shell.filename, 1816 expr, 1817 "Function used in"+ 1818 " expression but it returns %d values: %10q", 1819 len(objs), objs) 1820 } 1821 1822 return objs[0], nil 1823 } 1824 } 1825 1826 return nil, errors.NewEvalError(shell.filename, 1827 expr, "Failed to eval expression: %+v", expr) 1828 } 1829 1830 func (shell *Shell) executeSetenvAssign(assign *ast.AssignNode) error { 1831 for i := 0; i < len(assign.Names); i++ { 1832 name := assign.Names[i] 1833 value := assign.Values[i] 1834 err := shell.initVar(name, value) 1835 if err != nil { 1836 return err 1837 } 1838 obj, ok := shell.GetLocalvar(name.Ident) 1839 if !ok { 1840 return errors.NewEvalError(shell.filename, 1841 assign, 1842 "internal error: Setenv not setting local variable '%s'", 1843 name.Ident, 1844 ) 1845 } 1846 shell.Setenv(name.Ident, obj) 1847 } 1848 return nil 1849 } 1850 1851 func (shell *Shell) executeSetenvExec(assign *ast.ExecAssignNode) error { 1852 err := shell.executeExecAssign(assign) 1853 if err != nil { 1854 return err 1855 } 1856 for i := 0; i < len(assign.Names); i++ { 1857 name := assign.Names[i] 1858 obj, ok := shell.GetLocalvar(name.Ident) 1859 if !ok { 1860 return errors.NewEvalError(shell.filename, 1861 assign, 1862 "internal error: Setenv not setting local variable '%s'", 1863 name.Ident, 1864 ) 1865 } 1866 shell.Setenv(name.Ident, obj) 1867 } 1868 return nil 1869 } 1870 1871 func (shell *Shell) executeSetenv(v *ast.SetenvNode) error { 1872 var ( 1873 varValue sh.Obj 1874 ok bool 1875 assign = v.Assignment() 1876 ) 1877 1878 if assign != nil { 1879 switch assign.Type() { 1880 case ast.NodeAssign: 1881 return shell.executeSetenvAssign(assign.(*ast.AssignNode)) 1882 case ast.NodeExecAssign: 1883 return shell.executeSetenvExec(assign.(*ast.ExecAssignNode)) 1884 } 1885 return errors.NewEvalError(shell.filename, 1886 v, "Failed to eval setenv, invalid assignment type: %+v", 1887 assign) 1888 } 1889 1890 varValue, ok = shell.Getvar(v.Name) 1891 if !ok { 1892 return errors.NewEvalError(shell.filename, 1893 v, "Variable '%s' not set on shell %s", v.Name, 1894 shell.name, 1895 ) 1896 } 1897 shell.Setenv(v.Name, varValue) 1898 return nil 1899 } 1900 1901 func (shell *Shell) concatElements(expr *ast.ConcatExpr) (string, error) { 1902 value := "" 1903 1904 list := expr.List() 1905 1906 for i := 0; i < len(list); i++ { 1907 ec := list[i] 1908 1909 obj, err := shell.evalExpr(ec) 1910 1911 if err != nil { 1912 return "", err 1913 } 1914 1915 if obj.Type() != sh.StringType { 1916 return "", errors.NewEvalError(shell.filename, 1917 expr, "Impossible to concat elements of type %s", obj.Type()) 1918 } 1919 1920 value = value + obj.String() 1921 } 1922 1923 return value, nil 1924 } 1925 1926 func (shell *Shell) execCmdOutput(cmd ast.Node, 1927 getstderr, ignoreError bool) ([]byte, []byte, sh.Obj, error) { 1928 var ( 1929 outBuf, errBuf bytes.Buffer 1930 err error 1931 status sh.Obj 1932 ) 1933 if cmd.Type() != ast.NodeCommand && 1934 cmd.Type() != ast.NodePipe { 1935 return nil, nil, nil, errors.NewEvalError(shell.filename, 1936 cmd, "Invalid node type (%v). Expected command or pipe", 1937 cmd) 1938 } 1939 1940 bkStdout, bkStderr := shell.stdout, shell.stderr 1941 shell.SetStdout(&outBuf) 1942 if getstderr { 1943 shell.SetStderr(&errBuf) 1944 } 1945 defer func() { 1946 shell.SetStdout(bkStdout) 1947 shell.SetStderr(bkStderr) 1948 }() 1949 1950 if cmd.Type() == ast.NodeCommand { 1951 status, err = shell.executeCommand(cmd.(*ast.CommandNode)) 1952 } else { 1953 status, err = shell.executePipe(cmd.(*ast.PipeNode)) 1954 } 1955 1956 outb := outBuf.Bytes() 1957 errb := errBuf.Bytes() 1958 1959 trimnl := func(data []byte) []byte { 1960 if len(data) > 0 && data[len(data)-1] == '\n' { 1961 // remove the trailing new line 1962 // Why? because it's what user wants in 99.99% of times... 1963 1964 data = data[0 : len(data)-1] 1965 } 1966 return data[:] 1967 } 1968 1969 if ignoreError { 1970 err = nil 1971 } 1972 1973 return trimnl(outb), trimnl(errb), status, err 1974 } 1975 1976 func (shell *Shell) executeExecAssignCmd(v ast.Node) (stdout, stderr, status sh.Obj, err error) { 1977 assign := v.(*ast.ExecAssignNode) 1978 cmd := assign.Command() 1979 1980 mustIgnoreErr := len(assign.Names) > 1 1981 collectStderr := len(assign.Names) == 3 1982 1983 outb, errb, status, err := shell.execCmdOutput(cmd, collectStderr, mustIgnoreErr) 1984 if err != nil { 1985 return nil, nil, nil, err 1986 } 1987 1988 return sh.NewStrObj(string(outb)), sh.NewStrObj(string(errb)), status, nil 1989 } 1990 1991 func (shell *Shell) executeExecAssignFn(assign *ast.ExecAssignNode) ([]sh.Obj, error) { 1992 var ( 1993 err error 1994 fnValues []sh.Obj 1995 ) 1996 1997 cmd := assign.Command() 1998 if cmd.Type() != ast.NodeFnInv { 1999 return nil, errors.NewEvalError(shell.filename, 2000 cmd, "Invalid node type (%v). Expected function call", 2001 cmd) 2002 } 2003 2004 fnValues, err = shell.executeFnInv(cmd.(*ast.FnInvNode)) 2005 if err != nil { 2006 return nil, err 2007 } 2008 2009 if len(fnValues) != len(assign.Names) { 2010 return nil, errors.NewEvalError(shell.filename, 2011 assign, "Functions returns %d objects, but statement expects %d", 2012 len(fnValues), len(assign.Names)) 2013 } 2014 2015 return fnValues, nil 2016 } 2017 2018 func (shell *Shell) executeExecAssign(v *ast.ExecAssignNode) (err error) { 2019 exec := v.Command() 2020 switch exec.Type() { 2021 case ast.NodeFnInv: 2022 var values []sh.Obj 2023 values, err = shell.executeExecAssignFn(v) 2024 if err != nil { 2025 return err 2026 } 2027 err = shell.setvars(v.Names, values) 2028 case ast.NodeCommand, ast.NodePipe: 2029 var stdout, stderr, status sh.Obj 2030 stdout, stderr, status, err = shell.executeExecAssignCmd(v) 2031 if err != nil { 2032 return err 2033 } 2034 2035 err = shell.setcmdvars(v.Names, stdout, stderr, status) 2036 default: 2037 err = errors.NewEvalError(shell.filename, 2038 exec, "Invalid node type (%v). Expected function call, command or pipe", 2039 exec) 2040 } 2041 2042 return err 2043 } 2044 2045 func (shell *Shell) initVar(name *ast.NameNode, value ast.Expr) error { 2046 obj, err := shell.evalExpr(value) 2047 if err != nil { 2048 return err 2049 } 2050 return shell.newvar(name, obj) 2051 } 2052 2053 func (shell *Shell) executeVarAssign(v *ast.VarAssignDeclNode) error { 2054 assign := v.Assign 2055 if len(assign.Names) != len(assign.Values) { 2056 return errors.NewEvalError(shell.filename, 2057 assign, "Invalid multiple assignment. Different amount of variables and values: %s", 2058 assign, 2059 ) 2060 } 2061 2062 for i := 0; i < len(assign.Names); i++ { 2063 name := assign.Names[i] 2064 value := assign.Values[i] 2065 2066 err := shell.initVar(name, value) 2067 if err != nil { 2068 return err 2069 } 2070 } 2071 2072 return nil 2073 } 2074 2075 func (shell *Shell) executeVarExecAssign(v *ast.VarExecAssignDeclNode) (err error) { 2076 assign := v.ExecAssign 2077 exec := assign.Command() 2078 switch exec.Type() { 2079 case ast.NodeFnInv: 2080 var values []sh.Obj 2081 values, err = shell.executeExecAssignFn(assign) 2082 if err != nil { 2083 return err 2084 } 2085 shell.newvars(assign.Names, values) 2086 case ast.NodeCommand, ast.NodePipe: 2087 var stdout, stderr, status sh.Obj 2088 stdout, stderr, status, err = shell.executeExecAssignCmd(assign) 2089 if err != nil { 2090 return err 2091 } 2092 2093 shell.newcmdvars(assign.Names, stdout, stderr, status) 2094 default: 2095 err = errors.NewEvalError(shell.filename, 2096 exec, "Invalid node type (%v). Expected function call, command or pipe", 2097 exec) 2098 } 2099 2100 return err 2101 } 2102 2103 func (shell *Shell) executeAssignment(v *ast.AssignNode) error { 2104 if len(v.Names) != len(v.Values) { 2105 return errors.NewEvalError(shell.filename, 2106 v, "Invalid multiple assignment. Different amount of variables and values: %s", 2107 v, 2108 ) 2109 } 2110 2111 for i := 0; i < len(v.Names); i++ { 2112 name := v.Names[i] 2113 value := v.Values[i] 2114 2115 obj, err := shell.evalExpr(value) 2116 if err != nil { 2117 return err 2118 } 2119 2120 err = shell.setvar(name, obj) 2121 if err != nil { 2122 return err 2123 } 2124 } 2125 2126 return nil 2127 } 2128 2129 func (shell *Shell) evalIfArgument(arg ast.Node) (sh.Obj, error) { 2130 var ( 2131 obj sh.Obj 2132 err error 2133 ) 2134 2135 obj, err = shell.evalExpr(arg) 2136 if err != nil { 2137 return nil, err 2138 } else if obj == nil { 2139 return nil, errors.NewEvalError(shell.filename, 2140 arg, "lvalue doesn't yield value (%s)", arg) 2141 } 2142 2143 return obj, nil 2144 } 2145 2146 func (shell *Shell) evalIfArguments(n *ast.IfNode) (string, string, error) { 2147 var ( 2148 lobj, robj sh.Obj 2149 err error 2150 ) 2151 2152 lobj, err = shell.evalIfArgument(n.Lvalue()) 2153 2154 if err != nil { 2155 return "", "", err 2156 } 2157 2158 robj, err = shell.evalIfArgument(n.Rvalue()) 2159 2160 if err != nil { 2161 return "", "", err 2162 } 2163 2164 if lobj.Type() != sh.StringType { 2165 return "", "", errors.NewEvalError(shell.filename, 2166 n, "lvalue is not comparable: (%v) -> %s.", lobj, lobj.Type()) 2167 } 2168 2169 if robj.Type() != sh.StringType { 2170 return "", "", errors.NewEvalError(shell.filename, 2171 n, "rvalue is not comparable: (%v) -> %s.", lobj, lobj.Type()) 2172 } 2173 2174 lobjstr := lobj.(*sh.StrObj) 2175 robjstr := robj.(*sh.StrObj) 2176 2177 return lobjstr.Str(), robjstr.Str(), nil 2178 } 2179 2180 func (shell *Shell) executeIfEqual(n *ast.IfNode) ([]sh.Obj, error) { 2181 lstr, rstr, err := shell.evalIfArguments(n) 2182 2183 if err != nil { 2184 return nil, err 2185 } 2186 2187 if lstr == rstr { 2188 return shell.executeTree(n.IfTree(), false) 2189 } else if n.ElseTree() != nil { 2190 return shell.executeTree(n.ElseTree(), false) 2191 } 2192 2193 return nil, nil 2194 } 2195 2196 func (shell *Shell) executeIfNotEqual(n *ast.IfNode) ([]sh.Obj, error) { 2197 lstr, rstr, err := shell.evalIfArguments(n) 2198 2199 if err != nil { 2200 return nil, err 2201 } 2202 2203 if lstr != rstr { 2204 return shell.executeTree(n.IfTree(), false) 2205 } else if n.ElseTree() != nil { 2206 return shell.executeTree(n.ElseTree(), false) 2207 } 2208 2209 return nil, nil 2210 } 2211 2212 func (shell *Shell) executeFnInv(n *ast.FnInvNode) ([]sh.Obj, error) { 2213 var fnDef sh.FnDef 2214 2215 fnName := n.Name() 2216 if len(fnName) > 1 && fnName[0] == '$' { 2217 argVar := ast.NewVarExpr(token.NewFileInfo(n.Line(), n.Column()), fnName) 2218 2219 obj, err := shell.evalVariable(argVar) 2220 if err != nil { 2221 return nil, err 2222 } 2223 2224 if obj.Type() != sh.FnType { 2225 return nil, errors.NewEvalError(shell.filename, 2226 n, "Variable '%s' is not a function.", fnName) 2227 } 2228 2229 objfn := obj.(*sh.FnObj) 2230 fnDef = objfn.Fn() 2231 } else { 2232 fnObj, err := shell.GetFn(fnName) 2233 if err != nil { 2234 return nil, errors.NewEvalError(shell.filename, 2235 n, err.Error()) 2236 } 2237 fnDef = fnObj.Fn() 2238 } 2239 2240 fn := fnDef.Build() 2241 args, err := shell.evalArgExprs(n.Args()) 2242 if err != nil { 2243 return nil, err 2244 } 2245 2246 err = fn.SetArgs(args) 2247 if err != nil { 2248 return nil, errors.NewEvalError(shell.filename, 2249 n, err.Error()) 2250 } 2251 2252 fn.SetStdin(shell.stdin) 2253 fn.SetStdout(shell.stdout) 2254 fn.SetStderr(shell.stderr) 2255 2256 err = fn.Start() 2257 if err != nil { 2258 return nil, errors.NewEvalError(shell.filename, 2259 n, err.Error()) 2260 } 2261 2262 err = fn.Wait() 2263 if err != nil { 2264 return nil, errors.NewEvalError(shell.filename, 2265 n, err.Error()) 2266 } 2267 2268 return fn.Results(), nil 2269 } 2270 2271 func (shell *Shell) executeInfLoop(tr *ast.Tree) ([]sh.Obj, error) { 2272 var ( 2273 err error 2274 objs []sh.Obj 2275 ) 2276 2277 for { 2278 objs, err = shell.executeTree(tr, false) 2279 2280 runtime.Gosched() 2281 2282 type ( 2283 interruptedError interface { 2284 Interrupted() bool 2285 } 2286 2287 stopWalkingError interface { 2288 StopWalking() bool 2289 } 2290 ) 2291 2292 if errInterrupted, ok := err.(interruptedError); ok && errInterrupted.Interrupted() { 2293 break 2294 } 2295 2296 if errStopWalking, ok := err.(stopWalkingError); ok && errStopWalking.StopWalking() { 2297 return objs, err 2298 } 2299 2300 shell.Lock() 2301 2302 if shell.getIntr() { 2303 shell.setIntr(false) 2304 2305 if err != nil { 2306 err = newErrInterrupted(err.Error()) 2307 } else { 2308 err = newErrInterrupted("loop interrupted") 2309 } 2310 } 2311 2312 shell.Unlock() 2313 2314 if err != nil { 2315 break 2316 } 2317 } 2318 2319 return nil, err 2320 } 2321 2322 func (shell *Shell) executeFor(n *ast.ForNode) ([]sh.Obj, error) { 2323 shell.Lock() 2324 shell.looping = true 2325 shell.Unlock() 2326 2327 defer func() { 2328 shell.Lock() 2329 defer shell.Unlock() 2330 2331 shell.looping = false 2332 }() 2333 2334 if n.InExpr() == nil { 2335 return shell.executeInfLoop(n.Tree()) 2336 } 2337 2338 id := n.Identifier() 2339 inExpr := n.InExpr() 2340 2341 var ( 2342 obj sh.Obj 2343 err error 2344 ) 2345 2346 if inExpr.Type() == ast.NodeVarExpr { 2347 obj, err = shell.evalVariable(inExpr.(*ast.VarExpr)) 2348 } else if inExpr.Type() == ast.NodeListExpr { 2349 obj, err = shell.evalList(inExpr.(*ast.ListExpr)) 2350 } else if inExpr.Type() == ast.NodeFnInv { 2351 var objs []sh.Obj 2352 objs, err = shell.executeFnInv(inExpr.(*ast.FnInvNode)) 2353 if err != nil { 2354 return nil, err 2355 } 2356 2357 if len(objs) != 1 { 2358 return nil, errors.NewEvalError(shell.filename, 2359 inExpr, "Functions with multiple returns do not work as for 'in expression' yet: %v", inExpr) 2360 } 2361 2362 obj = objs[0] 2363 } else { 2364 return nil, errors.NewEvalError(shell.filename, 2365 inExpr, "Invalid expression in for loop: %s", inExpr.Type()) 2366 } 2367 2368 if err != nil { 2369 return nil, err 2370 } 2371 2372 col, err := sh.NewCollection(obj) 2373 if err != nil { 2374 return nil, errors.NewEvalError(shell.filename, 2375 inExpr, "error[%s] trying to iterate", err) 2376 } 2377 2378 for i := 0; i < col.Len(); i++ { 2379 val, err := col.Get(i) 2380 if err != nil { 2381 return nil, errors.NewEvalError(shell.filename, 2382 inExpr, "unexpected error[%s] during iteration", err) 2383 } 2384 shell.Newvar(id, val) 2385 objs, err := shell.executeTree(n.Tree(), false) 2386 2387 type ( 2388 interruptedError interface { 2389 Interrupted() bool 2390 } 2391 2392 stopWalkingError interface { 2393 StopWalking() bool 2394 } 2395 ) 2396 2397 if errInterrupted, ok := err.(interruptedError); ok && errInterrupted.Interrupted() { 2398 return nil, err 2399 } 2400 2401 if errStopWalking, ok := err.(stopWalkingError); ok && errStopWalking.StopWalking() { 2402 return objs, err 2403 } 2404 2405 shell.Lock() 2406 2407 if shell.getIntr() { 2408 shell.setIntr(false) 2409 shell.Unlock() 2410 2411 if err != nil { 2412 return nil, newErrInterrupted(err.Error()) 2413 } 2414 2415 return nil, newErrInterrupted("loop interrupted") 2416 } 2417 2418 shell.Unlock() 2419 2420 if err != nil { 2421 return nil, err 2422 } 2423 } 2424 2425 return nil, nil 2426 } 2427 2428 func (shell *Shell) executeFnDecl(n *ast.FnDeclNode) error { 2429 fnDef, err := newUserFnDef(n.Name(), shell, n.Args(), n.Tree()) 2430 if err != nil { 2431 return err 2432 } 2433 2434 shell.Newvar(n.Name(), sh.NewFnObj(fnDef)) 2435 shell.logf("Function %s declared on '%s'", n.Name(), shell.name) 2436 return nil 2437 } 2438 2439 func (shell *Shell) executeBindFn(n *ast.BindFnNode) error { 2440 if !shell.Interactive() { 2441 return errors.NewEvalError(shell.filename, 2442 n, "'bindfn' is not allowed in non-interactive mode.") 2443 } 2444 2445 fnDef, err := shell.GetFn(n.Name()) 2446 if err != nil { 2447 return errors.NewEvalError(shell.filename, 2448 n, err.Error()) 2449 } 2450 2451 shell.Setbindfn(n.CmdName(), fnDef.Fn()) 2452 return nil 2453 } 2454 2455 func (shell *Shell) executeIf(n *ast.IfNode) ([]sh.Obj, error) { 2456 op := n.Op() 2457 2458 if op == "==" { 2459 return shell.executeIfEqual(n) 2460 } else if op == "!=" { 2461 return shell.executeIfNotEqual(n) 2462 } 2463 2464 return nil, fmt.Errorf("invalid operation '%s'", op) 2465 } 2466 2467 func validateDirs(nashpath string, nashroot string) error { 2468 if nashpath == nashroot { 2469 return fmt.Errorf("invalid nashpath and nashroot, they are both[%s] but they must differ", nashpath) 2470 } 2471 err := validateDir(nashpath) 2472 if err != nil { 2473 return fmt.Errorf("invalid nashpath, user's config won't be loaded: error: %s", err) 2474 } 2475 err = validateDir(nashroot) 2476 if err != nil { 2477 return fmt.Errorf("invalid nashroot, stdlib/stdbin won't be available: error: %s", err) 2478 } 2479 return nil 2480 } 2481 2482 func validateDir(dir string) error { 2483 dir, err := filepath.EvalSymlinks(dir) 2484 if err != nil { 2485 return err 2486 } 2487 2488 info, err := os.Stat(dir) 2489 if err != nil { 2490 return err 2491 } 2492 if !info.IsDir() { 2493 return fmt.Errorf("%s is a file, expected a dir", dir) 2494 } 2495 if !filepath.IsAbs(dir) { 2496 return fmt.Errorf("%s is a relative path, expected a absolute path", dir) 2497 } 2498 return nil 2499 }