github.com/NeowayLabs/nash@v0.2.2-0.20200127205349-a227041ffd50/internal/sh/fncall.go (about)

     1  package sh
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  
     8  	"github.com/madlambda/nash/ast"
     9  	"github.com/madlambda/nash/errors"
    10  	"github.com/madlambda/nash/sh"
    11  )
    12  
    13  type (
    14  	FnArg struct {
    15  		Name       string
    16  		IsVariadic bool
    17  	}
    18  
    19  	UserFn struct {
    20  		argNames []sh.FnArg // argNames store parameter name
    21  		done     chan error // for async execution
    22  		results  []sh.Obj
    23  
    24  		name     string // debugging purposes
    25  		parent   *Shell
    26  		subshell *Shell
    27  
    28  		environ []string
    29  
    30  		stdin          io.Reader
    31  		stdout, stderr io.Writer
    32  
    33  		body           *ast.Tree
    34  		repr           string
    35  		closeAfterWait []io.Closer
    36  	}
    37  )
    38  
    39  func NewUserFn(name string, args []sh.FnArg, body *ast.Tree, parent *Shell) *UserFn {
    40  	fn := &UserFn{
    41  		name:     name,
    42  		argNames: args,
    43  		body:     body,
    44  		done:     make(chan error),
    45  		parent:   parent,
    46  		stdin:    parent.Stdin(),
    47  		stdout:   parent.Stdout(),
    48  		stderr:   parent.Stderr(),
    49  		subshell: NewSubShell(name, parent),
    50  	}
    51  
    52  	fn.subshell.SetTree(fn.body)
    53  	fn.subshell.SetRepr(fn.repr)
    54  	fn.subshell.SetDebug(fn.parent.debug)
    55  	fn.subshell.SetStdout(fn.stdout)
    56  	fn.subshell.SetStderr(fn.stderr)
    57  	fn.subshell.SetStdin(fn.stdin)
    58  	fn.subshell.SetEnviron(fn.environ)
    59  	return fn
    60  }
    61  
    62  func (fn *UserFn) ArgNames() []sh.FnArg { return fn.argNames }
    63  
    64  func (fn *UserFn) AddArgName(arg sh.FnArg) {
    65  	fn.argNames = append(fn.argNames, arg)
    66  }
    67  
    68  func (fn *UserFn) SetArgs(args []sh.Obj) error {
    69  	var (
    70  		isVariadic      bool
    71  		countNormalArgs int
    72  	)
    73  
    74  	for i, argName := range fn.argNames {
    75  		if argName.IsVariadic {
    76  			if i != len(fn.argNames)-1 {
    77  				return errors.NewError("variadic expansion must be last argument")
    78  			}
    79  			isVariadic = true
    80  		} else {
    81  			countNormalArgs++
    82  		}
    83  	}
    84  
    85  	if !isVariadic && len(args) != len(fn.argNames) {
    86  		return errors.NewError("Wrong number of arguments for function %s. "+
    87  			"Expected %d but found %d",
    88  			fn.name, len(fn.argNames), len(args))
    89  	}
    90  
    91  	if isVariadic {
    92  		if len(args) < countNormalArgs {
    93  			return errors.NewError("Wrong number of arguments for function %s. "+
    94  				"Expected at least %d arguments but found %d", fn.name,
    95  				countNormalArgs, len(args))
    96  		}
    97  
    98  		if len(args) == 0 {
    99  			// there's only a variadic (optional) argument
   100  			// and user supplied no argument...
   101  			// then only initialize the variadic variable to
   102  			// empty list
   103  			fn.subshell.Newvar(fn.argNames[0].Name, sh.NewListObj([]sh.Obj{}))
   104  			return nil
   105  		}
   106  	}
   107  
   108  	var i int
   109  	for i = 0; i < len(fn.argNames) && i < len(args); i++ {
   110  		arg := args[i]
   111  		argName := fn.argNames[i].Name
   112  		isVariadic := fn.argNames[i].IsVariadic
   113  
   114  		if isVariadic {
   115  			var valist []sh.Obj
   116  			for ; i < len(args); i++ {
   117  				arg = args[i]
   118  				valist = append(valist, arg)
   119  			}
   120  			valistarg := sh.NewListObj(valist)
   121  			fn.subshell.Newvar(argName, valistarg)
   122  		} else {
   123  			fn.subshell.Newvar(argName, arg)
   124  		}
   125  	}
   126  
   127  	// set remaining (variadic) list
   128  	if len(fn.argNames) > 0 && i < len(fn.argNames) {
   129  		last := fn.argNames[len(fn.argNames)-1]
   130  		if !last.IsVariadic {
   131  			return errors.NewError("internal error: optional arguments only for variadic parameter")
   132  		}
   133  
   134  		fn.subshell.Newvar(last.Name, sh.NewListObj([]sh.Obj{}))
   135  	}
   136  
   137  	return nil
   138  }
   139  
   140  func (fn *UserFn) Name() string { return fn.name }
   141  
   142  func (fn *UserFn) SetRepr(repr string) {
   143  	fn.repr = repr
   144  }
   145  
   146  func (fn *UserFn) closeDescriptors(closers []io.Closer) {
   147  	for _, fd := range closers {
   148  		fd.Close()
   149  	}
   150  }
   151  
   152  func (fn *UserFn) execute() ([]sh.Obj, error) {
   153  	if fn.body != nil {
   154  		return fn.subshell.ExecuteTree(fn.body)
   155  	}
   156  
   157  	return nil, fmt.Errorf("fn not properly created")
   158  }
   159  
   160  func (fn *UserFn) Start() error {
   161  	go func() {
   162  		var err error
   163  		fn.results, err = fn.execute()
   164  		fn.done <- err
   165  	}()
   166  
   167  	return nil
   168  }
   169  
   170  func (fn *UserFn) Results() []sh.Obj { return fn.results }
   171  
   172  func (fn *UserFn) Wait() error {
   173  	err := <-fn.done
   174  
   175  	fn.closeDescriptors(fn.closeAfterWait)
   176  	fn.subshell = nil
   177  	return err
   178  }
   179  
   180  func (fn *UserFn) SetEnviron(env []string) {
   181  	fn.environ = env
   182  }
   183  
   184  func (fn *UserFn) SetStderr(w io.Writer) {
   185  	fn.stderr = w
   186  }
   187  
   188  func (fn *UserFn) SetStdout(w io.Writer) {
   189  	fn.stdout = w
   190  }
   191  
   192  func (fn *UserFn) SetStdin(r io.Reader) {
   193  	fn.stdin = r
   194  }
   195  
   196  func (fn *UserFn) Stdin() io.Reader  { return fn.stdin }
   197  func (fn *UserFn) Stdout() io.Writer { return fn.stdout }
   198  func (fn *UserFn) Stderr() io.Writer { return fn.stderr }
   199  
   200  func (fn *UserFn) String() string {
   201  	if fn.body != nil {
   202  		return fn.body.String()
   203  	}
   204  	panic("fn not initialized")
   205  }
   206  
   207  func (fn *UserFn) StdoutPipe() (io.ReadCloser, error) {
   208  	pr, pw, err := os.Pipe()
   209  
   210  	if err != nil {
   211  		return nil, err
   212  	}
   213  
   214  	fn.subshell.SetStdout(pw)
   215  
   216  	// As fn doesn't fork, both fd can be closed after wait is called
   217  	fn.closeAfterWait = append(fn.closeAfterWait, pw, pr)
   218  	return pr, nil
   219  }