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

     1  package expressions
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/lmorg/murex/lang"
     7  	"github.com/lmorg/murex/lang/expressions/primitives"
     8  	"github.com/lmorg/murex/utils"
     9  )
    10  
    11  func errNotAllowedInFunctions(cmd []rune, desc string, token ...rune) error {
    12  	return fmt.Errorf("%s, `%s`, are not allowed inside inlined functions: %s(...)",
    13  		string(desc), string(token), string(cmd))
    14  }
    15  
    16  func (tree *ParserT) parseFunction(exec bool, cmd []rune, strOrVal varFormatting) ([]rune, primitives.FunctionT, error) {
    17  	params, err := tree.parseFunctionParameters(cmd)
    18  	if err != nil {
    19  		return nil, nil, fmt.Errorf("cannot parse `function(parameters...)`: %s", err.Error())
    20  	}
    21  	r := append(cmd, params...)
    22  
    23  	if !exec {
    24  		return r, nil, nil
    25  	}
    26  
    27  	fn := func() (*primitives.Value, error) {
    28  		val := new(primitives.Value)
    29  		var err error
    30  
    31  		fork := tree.p.Fork(lang.F_NO_STDIN | lang.F_CREATE_STDOUT)
    32  		params = append([]rune{' '}, params[1:len(params)-1]...)
    33  		block := append(cmd, params...)
    34  		val.ExitNum, err = fork.Execute(block)
    35  
    36  		//val.Error = fork.Stderr
    37  
    38  		if err != nil {
    39  			return val, fmt.Errorf("function `%s` compilation error: %s", string(cmd), err.Error())
    40  		}
    41  
    42  		if val.ExitNum != 0 {
    43  			return val, fmt.Errorf("function `%s` returned non-zero exit number (%d)", string(cmd), val.ExitNum)
    44  		}
    45  
    46  		b, err := fork.Stdout.ReadAll()
    47  		if err != nil {
    48  			return val, fmt.Errorf("function `%s` STDOUT read error: %s", string(cmd), err.Error())
    49  		}
    50  		b = utils.CrLfTrim(b)
    51  		val.DataType = fork.Stdout.GetDataType()
    52  		val.Value, err = formatBytes(b, val.DataType, strOrVal)
    53  		if err != nil {
    54  			return nil, fmt.Errorf("function `%s` STDOUT conversion error: %s", string(cmd), err.Error())
    55  		}
    56  
    57  		return val, err
    58  	}
    59  
    60  	return r, fn, nil
    61  }
    62  
    63  func (tree *ParserT) parseFunctionParameters(cmd []rune) ([]rune, error) {
    64  	var escape bool
    65  	start := tree.charPos
    66  	tree.charPos++
    67  
    68  	for ; tree.charPos < len(tree.expression); tree.charPos++ {
    69  		r := tree.expression[tree.charPos]
    70  
    71  		if escape {
    72  			if r == '\n' {
    73  				return nil, errNotAllowedInFunctions(cmd, "escaped line endings", []rune(`\\n`)...)
    74  			}
    75  
    76  			escape = false
    77  			continue
    78  		}
    79  
    80  		switch r {
    81  		case '#':
    82  			return nil, errNotAllowedInFunctions(cmd, "line comments", r)
    83  
    84  		case '/':
    85  			if tree.nextChar() == '#' {
    86  				if err := tree.parseCommentMultiLine(); err != nil {
    87  					return nil, err
    88  				}
    89  			}
    90  
    91  		case '\\':
    92  			escape = true
    93  
    94  		case ' ', '\t', '\r':
    95  			// whitespace. do nothing
    96  
    97  		case '\n':
    98  			// '\' escaped used at end of line
    99  			return nil, errNotAllowedInFunctions(cmd, "line feeds", []rune(`\n`)...)
   100  
   101  		case '?':
   102  			prev := tree.prevChar()
   103  			next := tree.nextChar()
   104  			if prev != ' ' && prev != '\t' &&
   105  				next != ' ' && next != '\t' {
   106  				continue
   107  			}
   108  			return nil, errNotAllowedInFunctions(cmd, "STDERR pipes", r)
   109  
   110  		case ';':
   111  			return nil, errNotAllowedInFunctions(cmd, "command terminators", r)
   112  
   113  		case '|':
   114  			if tree.nextChar() == '|' {
   115  				return nil, errNotAllowedInFunctions(cmd, "logical operators", []rune(`||`)...)
   116  			}
   117  			return nil, errNotAllowedInFunctions(cmd, "pipes", r)
   118  
   119  		case '&':
   120  			if tree.nextChar() == '&' {
   121  				return nil, errNotAllowedInFunctions(cmd, "logical operators", []rune(`&&`)...)
   122  			}
   123  
   124  		case '=':
   125  			switch tree.nextChar() {
   126  			case '>':
   127  				// generic pipe
   128  				return nil, errNotAllowedInFunctions(cmd, "generic pipes", []rune(`=>`)...)
   129  			default:
   130  				// assign value
   131  				continue
   132  			}
   133  
   134  		case '>':
   135  			switch tree.nextChar() {
   136  			case '>':
   137  				// redirect (append)
   138  				return nil, errNotAllowedInFunctions(cmd, "redirection pipes", []rune(`>>`)...)
   139  			default:
   140  				continue
   141  			}
   142  
   143  		case '(':
   144  			_, err := tree.parseParenthesis(false)
   145  			if err != nil {
   146  				return nil, err
   147  			}
   148  
   149  		case ')':
   150  			tree.charPos++
   151  			return tree.expression[start:tree.charPos], nil
   152  
   153  		case '%':
   154  			switch tree.nextChar() {
   155  			case '[':
   156  				// JSON array
   157  				tree.charPos++
   158  				_, _, err := tree.parseArray(false)
   159  				if err != nil {
   160  					return nil, err
   161  				}
   162  			case '{':
   163  				// JSON object
   164  				tree.charPos++
   165  				_, _, err := tree.parseObject(false)
   166  				if err != nil {
   167  					return nil, err
   168  				}
   169  				tree.charPos++
   170  			case '(':
   171  				// string
   172  				tree.charPos++
   173  				_, err := tree.parseParenthesis(false)
   174  				if err != nil {
   175  					return nil, err
   176  				}
   177  			default:
   178  				continue
   179  			}
   180  
   181  		case '{':
   182  			// block literal
   183  			_, err := tree.parseBlockQuote()
   184  			if err != nil {
   185  				return nil, err
   186  			}
   187  
   188  		case '}':
   189  			return nil, raiseError(tree.expression, nil, tree.charPos,
   190  				"unexpected closing bracket '}'")
   191  
   192  		case '\'', '"':
   193  			_, err := tree.parseString(r, r, false)
   194  			if err != nil {
   195  				return nil, err
   196  			}
   197  			tree.charPos++
   198  
   199  		case '$':
   200  			switch {
   201  			case tree.nextChar() == '{':
   202  				// subshell
   203  				_, _, err := tree.parseSubShell(false, r, varAsString)
   204  				if err != nil {
   205  					return nil, err
   206  				}
   207  			default:
   208  				// start scalar
   209  				_, _, _, err := tree.parseVarScalar(false, false, varAsString)
   210  				if err != nil {
   211  					return nil, raiseError(tree.expression, nil, tree.charPos, err.Error())
   212  				}
   213  			}
   214  
   215  		case '@':
   216  			prev := tree.prevChar()
   217  			next := tree.nextChar()
   218  			switch {
   219  			case prev != ' ' && prev != '\t' && prev != 0:
   220  				continue
   221  			case next == '{':
   222  				// subshell
   223  				_, _, err := tree.parseSubShell(false, r, varAsString)
   224  				if err != nil {
   225  					return nil, err
   226  				}
   227  			case isBareChar(tree.nextChar()):
   228  				// start scalar
   229  				_, _, err := tree.parseVarArray(false)
   230  				if err != nil {
   231  					return nil, err
   232  				}
   233  			default:
   234  				continue
   235  			}
   236  
   237  		case '-':
   238  			next := tree.nextChar()
   239  			switch {
   240  			case next == '>':
   241  				return nil, errNotAllowedInFunctions(cmd, "pipes", []rune(`->`)...)
   242  			default:
   243  				// assign value
   244  				continue
   245  			}
   246  
   247  		default:
   248  			// assign value
   249  			continue
   250  		}
   251  	}
   252  
   253  	return nil, raiseError(tree.expression, nil, start,
   254  		fmt.Sprintf("unexpected end of code. Missing closing parenthesis from inlined function `%s(...)`",
   255  			string(cmd)))
   256  }