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 }