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 }