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  }