github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/lang/fork.go (about)

     1  package lang
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  
     8  	"github.com/lmorg/murex/builtins/pipes/null"
     9  	"github.com/lmorg/murex/builtins/pipes/streams"
    10  	"github.com/lmorg/murex/builtins/pipes/term"
    11  	"github.com/lmorg/murex/debug"
    12  	"github.com/lmorg/murex/lang/runmode"
    13  	"github.com/lmorg/murex/lang/state"
    14  	"github.com/lmorg/murex/lang/types"
    15  )
    16  
    17  const (
    18  	// F_DEFAULTS is forking with within the existing function
    19  	F_DEFAULTS = 0
    20  
    21  	// F_NEW_MODULE will skip the stage of inheriting the module name from the
    22  	// calling function. You will still then need to specify that module name
    23  	// yourself. eg
    24  	//
    25  	//     fork := p.Fork(F_SHELL|F_NEW_MODULE)
    26  	//     fork.Module = "package/module"
    27  	//     exitNum, err := fork.Execute([]rune{})
    28  	F_NEW_MODULE = 1 << iota
    29  
    30  	// F_FUNCTION will assign a bunch of sane default properties for a function
    31  	// call
    32  	F_FUNCTION
    33  
    34  	// F_PARENT_VARTABLE will bypass the automatic forking of the var table.
    35  	// The plan is to make this the default because it's what you'd expect to
    36  	// use inside builtins
    37  	F_PARENT_VARTABLE
    38  
    39  	// F_NEW_VARTABLE will fork the variable table (not needed when using
    40  	// F_FUNCTION)
    41  	// For reasons I haven't got to the bottom of yet, this is rather glitchy
    42  	// inside builtins.
    43  	F_NEW_VARTABLE
    44  
    45  	// F_NEW_CONFIG will fork the config table - eg when calling a new function
    46  	// (not needed when calling F_FUNCTION)
    47  	F_NEW_CONFIG
    48  
    49  	// F_NEW_TESTS will start a new scope for the testing framework (not needed
    50  	// when calling F_FUNCTION)
    51  	F_NEW_TESTS
    52  
    53  	// F_BACKGROUND this process will run in the background
    54  	F_BACKGROUND
    55  
    56  	// F_CREATE_STDIN will create a new stdin stdio.Io interface
    57  	F_CREATE_STDIN
    58  
    59  	// F_CREATE_STDOUT will create a new stdout stdio.Io interface
    60  	F_CREATE_STDOUT
    61  
    62  	// F_CREATE_STDERR will create a new stderr stdio.Io interface
    63  	F_CREATE_STDERR
    64  
    65  	// F_NO_STDIN will ensure stdin will be a nil interface
    66  	F_NO_STDIN
    67  
    68  	// F_NO_STDOUT will ensure stdout will be a nil interface
    69  	F_NO_STDOUT
    70  
    71  	// F_NO_STDERR will ensure stderr will be a nil interface
    72  	F_NO_STDERR
    73  
    74  	// F_PREVIEW
    75  	F_PREVIEW
    76  )
    77  
    78  var (
    79  	ShowPrompt = make(chan bool, 1)
    80  	HidePrompt = make(chan bool, 1)
    81  
    82  	ModuleRunModes map[string]runmode.RunMode = make(map[string]runmode.RunMode)
    83  )
    84  
    85  // Fork is a forked process
    86  type Fork struct {
    87  	*Process
    88  	fidRegistered bool
    89  	newTestScope  bool
    90  	preview       bool
    91  }
    92  
    93  const ForkSuffix = " (fork)"
    94  
    95  // Fork will create a new handle for executing a code block
    96  func (p *Process) Fork(flags int) *Fork {
    97  	fork := new(Fork)
    98  	fork.Process = new(Process)
    99  	fork.SetTerminatedState(true)
   100  	fork.Forks = p.Forks
   101  
   102  	fork.State.Set(state.MemAllocated)
   103  	fork.Background.Set(flags&F_BACKGROUND != 0 || p.Background.Get())
   104  
   105  	fork.IsMethod = p.IsMethod
   106  	fork.OperatorLogicAnd = p.OperatorLogicAnd
   107  	fork.OperatorLogicOr = p.OperatorLogicOr
   108  	fork.IsNot = p.IsNot
   109  
   110  	fork.Previous = p.Previous
   111  	fork.Next = p.Next
   112  
   113  	fork.preview = flags&F_PREVIEW != 0
   114  
   115  	if p.Id == ShellProcess.Id {
   116  		fork.ExitNum = ShellExitNum
   117  	}
   118  
   119  	if flags&F_NEW_MODULE == 0 {
   120  		fork.FileRef = p.FileRef
   121  	}
   122  
   123  	if flags&F_FUNCTION != 0 {
   124  		fork.Scope = fork.Process
   125  		fork.Parent = fork.Process
   126  		fork.Context, fork.Done = context.WithCancel(context.Background())
   127  		fork.Kill = fork.Done
   128  
   129  		fork.Variables = NewVariables(fork.Process)
   130  		GlobalFIDs.Register(fork.Process)
   131  		fork.fidRegistered = true
   132  
   133  		fork.Config = p.Config.Copy()
   134  
   135  		fork.newTestScope = true
   136  		fork.Tests = NewTests(fork.Process)
   137  
   138  	} else {
   139  		fork.Scope = p.Scope
   140  		fork.Name.Set(p.Name.String())
   141  		//fork.Parameters.CopyFrom(&p.Parameters)
   142  		fork.Context, fork.Done = p.Context, p.Done
   143  
   144  		if p.Scope.RunMode > runmode.Default {
   145  			fork.RunMode = p.Scope.RunMode
   146  		}
   147  		if p.RunMode > runmode.Default {
   148  			fork.RunMode = p.RunMode
   149  		}
   150  
   151  		switch {
   152  		case flags&F_PARENT_VARTABLE != 0:
   153  			fork.Parent = p.Parent
   154  			fork.Variables = p.Variables
   155  			fork.Id = p.Id
   156  
   157  		case flags&F_NEW_VARTABLE != 0:
   158  			fork.Parent = p.Parent
   159  			fork.Variables = p.Variables
   160  			fork.Name.Append(ForkSuffix)
   161  			GlobalFIDs.Register(fork.Process)
   162  			fork.fidRegistered = true
   163  
   164  		default:
   165  			//panic("must include either F_PARENT_VARTABLE or F_NEW_VARTABLE")
   166  			fork.Parent = p.Parent
   167  			fork.Variables = NewVariables(fork.Process)
   168  			fork.Variables = p.Variables
   169  			fork.Name.Append(ForkSuffix)
   170  			GlobalFIDs.Register(fork.Process)
   171  			fork.fidRegistered = true
   172  		}
   173  
   174  		if flags&F_NEW_CONFIG != 0 {
   175  			fork.Config = p.Config.Copy()
   176  		} else {
   177  			fork.Config = p.Config
   178  		}
   179  
   180  		if flags&F_NEW_TESTS != 0 {
   181  			fork.newTestScope = true
   182  			fork.Tests = NewTests(fork.Process)
   183  		} else {
   184  			fork.Tests = p.Tests
   185  		}
   186  	}
   187  
   188  	switch {
   189  	case flags&F_CREATE_STDIN != 0:
   190  		fork.Stdin = streams.NewStdin()
   191  	case flags&F_NO_STDIN != 0:
   192  		fork.Stdin = streams.NewStdin()
   193  		fork.Stdin.SetDataType(types.Null)
   194  	default:
   195  		fork.Stdin = p.Stdin
   196  	}
   197  
   198  	switch {
   199  	case flags&F_CREATE_STDOUT != 0:
   200  		fork.Stdout = streams.NewStdin()
   201  	case flags&F_NO_STDOUT != 0:
   202  		if debug.Enabled {
   203  			// This is TermErr despite being a Stdout stream because it is a debug
   204  			// stream so we don't want to taint stdout with unexpected output.
   205  			fork.Stdout = term.NewErr(true)
   206  		} else {
   207  			fork.Stdout = new(null.Null)
   208  		}
   209  	default:
   210  		fork.Stdout = p.Stdout
   211  	}
   212  
   213  	switch {
   214  	case flags&F_CREATE_STDERR != 0:
   215  		fork.Stderr = streams.NewStdin()
   216  	case flags&F_NO_STDERR != 0:
   217  		if debug.Enabled {
   218  			// This is TermErr despite being a Stdout stream because it is a debug
   219  			// stream so we don't want to taint stdout with unexpected output.
   220  			fork.Stderr = term.NewErr(true)
   221  		} else {
   222  			fork.Stderr = new(null.Null)
   223  		}
   224  	default:
   225  		fork.Stderr = p.Stderr
   226  	}
   227  
   228  	return fork
   229  }
   230  
   231  // Execute will run a murex code block
   232  func (fork *Fork) Execute(block []rune) (exitNum int, err error) {
   233  	switch {
   234  	case fork.FileRef == nil:
   235  		panic("fork.FileRef == nil in (fork *Fork).Execute()")
   236  	case fork.FileRef.Source == nil:
   237  		panic("fork.FileRef.Source == nil in (fork *Fork).Execute()")
   238  	case fork.FileRef.Source.Module == "":
   239  		panic("missing module name in (fork *Fork).Execute()")
   240  	case fork.Name.String() == "":
   241  		panic("missing function name in (fork *Fork).Execute()")
   242  	}
   243  
   244  	moduleRunMode := ModuleRunModes[fork.FileRef.Source.Module]
   245  	if moduleRunMode > 0 && fork.RunMode == 0 {
   246  		fork.RunMode = moduleRunMode
   247  	}
   248  
   249  	fork.Stdout.Open()
   250  	fork.Stderr.Open()
   251  
   252  	if len(block) > 2 && block[0] == '{' && block[len(block)-1] == '}' {
   253  		block = block[1 : len(block)-1]
   254  	}
   255  
   256  	if fork.fidRegistered {
   257  		defer deregisterProcess(fork.Process)
   258  	} else {
   259  		defer fork.SetTerminatedState(true)
   260  		defer fork.Stdout.Close()
   261  		defer fork.Stderr.Close()
   262  	}
   263  
   264  	tree, err := ParseBlock(block)
   265  	if err != nil {
   266  		return 1, err
   267  	}
   268  
   269  	procs, errNo := compile(tree, fork.Process)
   270  	if errNo != 0 {
   271  		errMsg := fmt.Sprintf("compilation Error at %d,%d+0 (%s): %s",
   272  			fork.FileRef.Line, fork.FileRef.Column, fork.FileRef.Source.Module, errMessages[errNo])
   273  		fork.Stderr.Writeln([]byte(errMsg))
   274  		return errNo, errors.New(errMsg)
   275  	}
   276  	if len(*procs) == 0 {
   277  		return 0, nil
   278  	}
   279  
   280  	id := fork.Process.Forks.add(procs)
   281  	defer fork.Process.Forks.delete(id)
   282  
   283  	if fork.preview {
   284  		err := previewCache.compile(tree, procs)
   285  		if err != nil {
   286  			return 0, err
   287  		}
   288  	}
   289  
   290  	if !fork.Background.Get() {
   291  		ForegroundProc.Set(&(*procs)[0])
   292  	}
   293  
   294  	// Support for different run modes:
   295  	switch fork.RunMode {
   296  	case runmode.Default, runmode.Normal:
   297  		exitNum = runModeNormal(procs)
   298  
   299  	case runmode.BlockUnsafe, runmode.FunctionUnsafe, runmode.ModuleUnsafe:
   300  		_ = runModeNormal(procs)
   301  		exitNum = 0
   302  
   303  	case runmode.BlockTry, runmode.FunctionTry, runmode.ModuleTry:
   304  		exitNum = runModeTry(procs, false)
   305  
   306  	case runmode.BlockTryPipe, runmode.FunctionTryPipe, runmode.ModuleTryPipe:
   307  		exitNum = runModeTryPipe(procs, false)
   308  
   309  	case runmode.BlockTryErr, runmode.FunctionTryErr, runmode.ModuleTryErr:
   310  		exitNum = runModeTry(procs, true)
   311  
   312  	case runmode.BlockTryPipeErr, runmode.FunctionTryPipeErr, runmode.ModuleTryPipeErr:
   313  		exitNum = runModeTryPipe(procs, true)
   314  
   315  	default:
   316  		panic("unknown run mode")
   317  	}
   318  
   319  	if fork.newTestScope {
   320  		fork.Tests.ReportMissedTests(fork.Process)
   321  
   322  		testAutoReport, configErr := fork.Config.Get("test", "auto-report", types.Boolean)
   323  		if configErr == nil && testAutoReport.(bool) {
   324  			err = fork.Tests.WriteResults(fork.Config, ShellProcess.Stderr)
   325  			if err != nil {
   326  				message := fmt.Sprintf("Error generating test results: %s.", err.Error())
   327  				ShellProcess.Stderr.Writeln([]byte(message))
   328  			}
   329  		}
   330  	}
   331  
   332  	return
   333  }