github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/lang/expressions/parse_lambda.go (about) 1 package expressions 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "strings" 8 9 "github.com/lmorg/murex/lang" 10 "github.com/lmorg/murex/lang/expressions/symbols" 11 "github.com/lmorg/murex/lang/types" 12 "github.com/lmorg/murex/utils" 13 ) 14 15 var errCancelled = errors.New("cancelled") 16 17 func treePlusPlus(tree *ParserT) { tree.charPos++ } 18 19 func (tree *ParserT) parseLambdaExecFalse(sigil rune, varName []rune) ([]rune, error) { 20 defer treePlusPlus(tree) 21 22 r, _, err := tree.parseSubShell(false, sigil, varAsValue) 23 if err != nil { 24 return r, err 25 } 26 27 return r, nil 28 } 29 30 func (tree *ParserT) parseLambdaExecTrue(varName []rune, sigil rune, dt string) ([]rune, interface{}, string, error) { 31 // no `exec` boolean here because this method should only be invoked when `exec == true` 32 defer treePlusPlus(tree) 33 if tree.p == nil { 34 panic("`tree.p` is undefined") 35 } 36 37 r, block, err := tree.parseLambdaGetSubShellBlock() 38 if err != nil { 39 return nil, nil, "", err 40 } 41 42 return _parseLambdaExecTrue(tree.p, varName, sigil, dt, r, block, tree.StrictArrays()) 43 } 44 45 type parseLambdaExecTrueT func() ([]rune, any, string, error) 46 47 func _parseLambdaExecTrue(p *lang.Process, varName []rune, sigil rune, dt string, r []rune, block []rune, strictArrays bool) ([]rune, any, string, error) { 48 path := string(varName) 49 v, err := p.Variables.GetValue(path) 50 if err != nil { 51 return nil, nil, "", err 52 } 53 54 if dt == "" { 55 // TODO: test me please 56 dt = p.Variables.GetDataType(path) 57 } 58 59 switch t := v.(type) { 60 case nil: 61 if strictArrays { 62 return nil, nil, "", fmt.Errorf("cannot run lambda: value is a null object") 63 } 64 return parseLambdaArray(p, sigil, []string{}, dt, r, block) 65 66 /*case string: 67 return parseLambdaArray(tree, []string{t}, dt) 68 case []byte: 69 return parseLambdaArray(tree, []string{string(t)}, dt) 70 case []rune: 71 return parseLambdaArray(tree, []string{string(t)}, dt)*/ 72 73 /*case string: 74 return parseLambdaString(tree, t, dt) 75 case []byte: 76 return parseLambdaString(tree, string(t), dt) 77 case []rune: 78 return parseLambdaString(tree, string(t), dt)*/ 79 80 case []string: 81 return parseLambdaArray(p, sigil, t, dt, r, block) 82 case []float64: 83 return parseLambdaArray(p, sigil, t, dt, r, block) 84 case []int: 85 return parseLambdaArray(p, sigil, t, dt, r, block) 86 case []bool: 87 return parseLambdaArray(p, sigil, t, dt, r, block) 88 89 case []interface{}: 90 return parseLambdaArray(p, sigil, t, dt, r, block) 91 case map[string]string: 92 return parseLambdaMap(p, sigil, t, dt, r, block) 93 case map[string]interface{}: 94 return parseLambdaMap(p, sigil, t, dt, r, block) 95 96 default: 97 return nil, nil, "", fmt.Errorf("cannot run lambda: expecting an array or map, instead got '%T' in '%s' (%s)", t, path, dt) 98 } 99 } 100 101 func (tree *ParserT) parseLambdaStatement(exec bool, sigil rune) ([]rune, interface{}, string, error) { 102 if exec { 103 if tree.p.IsMethod { 104 return tree.parseLambdaStdinExecTrue(sigil) 105 106 } else { 107 r := tree.expression[tree.charPos] 108 return nil, nil, "", raiseError(tree.expression, nil, tree.charPos, fmt.Sprintf("%s '%s' (%d)", 109 errMessage[symbols.Unexpected], string(r), r)) 110 } 111 112 } else { 113 r, err := tree.parseLambdaExecFalse(sigil, nil) 114 return r, nil, "", err 115 } 116 } 117 118 func (tree *ParserT) parseLambdaStdinExecTrue(sigil rune) ([]rune, interface{}, string, error) { 119 dataType := tree.p.Stdin.GetDataType() 120 b, err := tree.p.Stdin.ReadAll() 121 if err != nil { 122 return nil, nil, "", err 123 } 124 125 name := fmt.Sprintf("_stdin_%d", tree.p.Id) 126 err = tree.p.Variables.Set(tree.p, name, b, dataType) 127 if err != nil { 128 return nil, nil, "", fmt.Errorf("unable to set temporary variable '%s' for piped lambda: %s", name, err.Error()) 129 } 130 131 r, v, dt, err := tree.parseLambdaExecTrue([]rune(name), sigil, dataType) 132 if err != nil { 133 return r, v, dt, err 134 } 135 136 err = tree.p.Variables.Unset(name) 137 if err != nil { 138 return r, v, dt, fmt.Errorf("unable to unset temporary variable '%s' for piped lambda: %s", name, err.Error()) 139 } 140 141 return r, v, dt, nil 142 } 143 144 var ( 145 errUnableToSetLambdaVar = "unable to set `$.`: %s" 146 errUnableToGetLambdaVar = "unable to retrieve value of `$.`: %s" 147 errUnableToUpdateValue = "cannot update value from lambda: %s" 148 149 //rxLineSeparator = regexp.MustCompile(`(\r*\n)+`) 150 ) 151 152 const ( 153 LAMBDA_ITERATION = "i" 154 LAMBDA_KEY = "k" 155 LAMBDA_VALUE = "v" 156 ) 157 158 func writeKeyValVariable[K comparable](p *lang.Process, iteration int, key K, value any) error { 159 element, err := json.Marshal(map[string]interface{}{ 160 LAMBDA_ITERATION: iteration, 161 LAMBDA_KEY: key, 162 LAMBDA_VALUE: value, 163 }) 164 if err != nil { 165 return fmt.Errorf("unable to encode element: %s", err.Error()) 166 } 167 168 return p.Variables.Set(p, "", string(element), types.Json) 169 } 170 171 func readKeyValVariable(p *lang.Process) (any, any, error) { 172 kv, err := p.Variables.GetValue("") 173 if err != nil { 174 return nil, nil, fmt.Errorf(errUnableToGetLambdaVar, err.Error()) 175 } 176 177 switch t := kv.(type) { 178 case map[string]any: 179 return t[LAMBDA_KEY], t[LAMBDA_VALUE], nil 180 default: 181 return nil, nil, fmt.Errorf("expecting $. to be '{%s: str, %s ...}', instead got '%T'", LAMBDA_KEY, LAMBDA_VALUE, kv) 182 } 183 } 184 185 /*func parseLambdaString(tree *ParserT, t string, path string) ([]rune, interface{}, error) { 186 var ( 187 item interface{} 188 err error 189 r []rune 190 j int 191 fn primitives.FunctionT 192 ) 193 194 split := rxLineSeparator.Split(t, -1) 195 array := make([]any, len(split)) 196 197 for i := range split { 198 //tree.charPos = pos 199 err = tree.p.Variables.Set(tree.p, "", split[i], types.String) 200 if err != nil { 201 return nil, nil, fmt.Errorf(errUnableToSetLambdaVar, err.Error()) 202 } 203 204 r, fn, err = tree.parseSubShell(true, '$', varAsValue) 205 if err != nil { 206 return nil, nil, err 207 } 208 val, err := fn() 209 item = val.Value 210 if err != nil { 211 return nil, nil, err 212 } 213 switch t := item.(type) { 214 case string: 215 if len(t) > 0 { 216 array[j] = item 217 j++ 218 } 219 case bool: 220 if t { 221 array[j] = split[i] 222 j++ 223 } 224 default: 225 array[j] = item 226 j++ 227 } 228 } 229 230 return r, array[:j], nil 231 }*/ 232 233 func parseLambdaArray[V any](p *lang.Process, sigil rune, t []V, dt string, r []rune, block []rune) ([]rune, interface{}, string, error) { 234 var ( 235 array []any 236 stdout []string 237 ) 238 239 for i := range t { 240 if p.HasCancelled() { 241 return nil, nil, "", errCancelled 242 } 243 244 err := writeKeyValVariable(p, i+1, i, t[i]) 245 if err != nil { 246 return nil, nil, "", fmt.Errorf(errUnableToSetLambdaVar, err.Error()) 247 } 248 249 exitNum, b, err := parseLambdaRunSubShell(p, block) 250 if err != nil { 251 return nil, nil, "", err 252 } 253 254 if len(b) > 0 { 255 stdout = append(stdout, string(utils.CrLfTrim(b))) 256 continue 257 } 258 259 if exitNum > 0 { 260 continue 261 } 262 263 newKey, newVal, err := readKeyValVariable(p) 264 if err != nil { 265 return nil, nil, "", fmt.Errorf(errUnableToUpdateValue, err.Error()) 266 } 267 if fmt.Sprint(newKey) != fmt.Sprint(i) { 268 return nil, nil, "", fmt.Errorf("arrays cannot have their $.%s changed: old key '%v', new key '%v'", LAMBDA_KEY, i, newKey) 269 } 270 271 array = append(array, newVal) 272 } 273 274 switch sigil { 275 case '$': 276 if len(stdout) > 0 { 277 s := strings.Join(stdout, "\n") 278 return r, s, types.String, nil 279 } 280 b, err := lang.MarshalData(p, dt, array) 281 return r, string(b), dt, err 282 283 case '@': 284 if len(stdout) > 0 { 285 return r, stdout, types.Json, nil 286 } 287 return r, array, types.Json, nil 288 289 default: 290 panic("uncaught sigil in switch statement") 291 } 292 } 293 294 func parseLambdaMap[K comparable, V any](p *lang.Process, sigil rune, t map[K]V, dt string, r []rune, block []rune) ([]rune, interface{}, string, error) { 295 var ( 296 object = make(map[any]interface{}) 297 stdout []string 298 ) 299 300 var i int 301 for key, value := range t { 302 if p.HasCancelled() { 303 return nil, nil, "", errCancelled 304 } 305 306 i++ 307 err := writeKeyValVariable(p, i, key, value) 308 if err != nil { 309 return nil, nil, "", fmt.Errorf(errUnableToSetLambdaVar, err.Error()) 310 } 311 312 exitNum, b, err := parseLambdaRunSubShell(p, block) 313 if err != nil { 314 return nil, nil, "", err 315 } 316 317 if len(b) > 0 { 318 stdout = append(stdout, string(utils.CrLfTrim(b))) 319 continue 320 } 321 322 if exitNum > 0 { 323 continue 324 } 325 326 newKey, newVal, err := readKeyValVariable(p) 327 if err != nil { 328 return nil, nil, "", fmt.Errorf(errUnableToUpdateValue, err.Error()) 329 } 330 331 object[newKey] = newVal 332 } 333 334 switch sigil { 335 case '$': 336 if len(stdout) > 0 { 337 s := strings.Join(stdout, "\n") 338 return r, s, types.String, nil 339 } 340 b, err := lang.MarshalData(p, dt, object) 341 return r, string(b), dt, err 342 343 case '@': 344 if len(stdout) > 0 { 345 return r, stdout, types.Json, nil 346 } 347 return r, object, types.Json, nil 348 349 default: 350 panic("uncaught sigil in switch statement") 351 } 352 } 353 354 func (tree *ParserT) parseLambdaGetSubShellBlock() ([]rune, []rune, error) { 355 start := tree.charPos 356 357 tree.charPos += 2 358 359 _, err := tree.parseBlockQuote() 360 if err != nil { 361 return nil, nil, err 362 } 363 364 value := tree.expression[start : tree.charPos+1] 365 block := tree.expression[start+2 : tree.charPos] 366 367 return value, block, nil 368 } 369 370 func parseLambdaRunSubShell(p *lang.Process, block []rune) (int, []byte, error) { 371 fork := p.Fork(lang.F_NO_STDIN | lang.F_CREATE_STDOUT | lang.F_NO_STDERR | lang.F_PARENT_VARTABLE) 372 exitNum, err := fork.Execute(block) 373 if err != nil { 374 return 1, nil, fmt.Errorf("subshell failed: %s", err.Error()) 375 } 376 377 b, err := fork.Stdout.ReadAll() 378 if err != nil { 379 return 1, nil, fmt.Errorf("unable to read from stdout: %s", err.Error()) 380 } 381 382 if fork.Stdout.GetDataType() == types.Boolean { 383 if types.IsTrue(b, exitNum) { 384 if string(b) == types.TrueString { 385 return 0, nil, nil 386 } 387 return 0, b, nil 388 } 389 return 1, nil, nil 390 } 391 392 return exitNum, b, err 393 }