github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/builtins/core/structs/switch.go (about) 1 package structs 2 3 import ( 4 "fmt" 5 6 "github.com/lmorg/murex/lang" 7 "github.com/lmorg/murex/lang/expressions" 8 "github.com/lmorg/murex/lang/types" 9 "github.com/lmorg/murex/utils" 10 "github.com/lmorg/murex/utils/humannumbers" 11 ) 12 13 func init() { 14 lang.DefineFunction("switch", cmdSwitch, types.Any) 15 } 16 17 const ( 18 errSwitchParameters = "%s parameters supplied. Please read the `switch` docs for how to use. eg `murex-docs switch`" 19 errReferToDocs = "Please read the `switch` docs for how to use. eg `murex-docs switch`" 20 ) 21 22 func cmdSwitch(p *lang.Process) error { 23 switch p.Parameters.Len() { 24 case 0: 25 return fmt.Errorf(errSwitchParameters, "no") 26 case 1: 27 return switchLogic(p, false, "") 28 case 2: 29 param, _ := p.Parameters.String(0) 30 return switchLogic(p, true, param) 31 default: 32 return fmt.Errorf(errSwitchParameters, "too many") 33 } 34 } 35 36 func switchLogic(p *lang.Process, byVal bool, val string) error { 37 var loc int 38 if byVal { 39 loc = 1 40 } 41 42 block, err := p.Parameters.Block(loc) 43 if err != nil { 44 return err 45 } 46 47 swt, err := expressions.ParseSwitch(p, block) 48 if err != nil { 49 return err 50 } 51 52 var prevIfPassed bool 53 54 for i, token := range swt { 55 switch token.Condition { 56 case "if", "case": 57 caseIf, thenBlock, err := validateStatementParameters(token, i, byVal) 58 if err != nil { 59 return err 60 } 61 62 var pass bool 63 if byVal { 64 pass, err = compareConditional(p, val, caseIf) 65 if err != nil { 66 return fmt.Errorf("error comparing %s statement, %s conditional:\n%s", 67 humannumbers.Ordinal(i+1), token.Condition, err.Error()) 68 } 69 } else { 70 pass, err = executeConditional(p, caseIf) 71 if err != nil { 72 return fmt.Errorf("error executing %s statement, %s conditional:\n%s", 73 humannumbers.Ordinal(i+1), token.Condition, err.Error()) 74 } 75 } 76 77 if pass { 78 err = executeThen(p, thenBlock) 79 if err != nil { 80 return fmt.Errorf("error executing %s statement, then block:\n%s", 81 humannumbers.Ordinal(i+1), err.Error()) 82 } 83 84 switch swt[i].Condition { 85 case "if": 86 prevIfPassed = true 87 continue 88 case "case": 89 return nil 90 } 91 } 92 93 case "default", "catch", "else": 94 if prevIfPassed { 95 return nil 96 } 97 98 _, thenBlock, err := validateStatementParameters(token, i, byVal) 99 if err != nil { 100 return err 101 } 102 103 err = executeThen(p, thenBlock) 104 if err != nil { 105 return fmt.Errorf("error executing %s statement, catch block:\n%s", 106 humannumbers.Ordinal(i+1), err.Error()) 107 } 108 109 return nil 110 111 default: 112 return fmt.Errorf("error executing %s statement, `%s` is not a valid statement.\nExpecting `case`, `if`, `catch`", 113 humannumbers.Ordinal(i+1), token.Condition) 114 } 115 } 116 117 if !prevIfPassed { 118 p.ExitNum = 1 119 } 120 121 return nil 122 } 123 124 func validateStatementParameters(token *expressions.SwitchT, i int, byVal bool) ([]rune, []rune, error) { 125 var adjust int 126 127 switch token.Condition { 128 case "if", "case": 129 switch token.ParametersLen() { 130 case 0: 131 return nil, nil, 132 fmt.Errorf("missing parameters for %s statement (%s)\n%s", 133 humannumbers.Ordinal(i+1), token.Condition, errReferToDocs) 134 case 1: 135 return nil, nil, 136 fmt.Errorf("too few parameters for %s statement (%s)\nExpected: conditional then { code block }\nFound: %s\n%s", 137 humannumbers.Ordinal(i+1), token.Condition, token.ParametersStringAll(), errReferToDocs) 138 139 case 3: 140 if token.ParametersString(1) != "then" { 141 return nil, nil, 142 fmt.Errorf("too many parameters for %s statement (%s) or typo in statements.\nExpecting 'then' statement\nFound: '%s'\n%s", 143 humannumbers.Ordinal(i+1), token.Condition, token.ParametersStringAll(), errReferToDocs) 144 } 145 adjust = 1 146 fallthrough 147 148 case 2: 149 thenBlock, err := token.Block(1 + adjust) 150 if err != nil { 151 return nil, nil, 152 fmt.Errorf("cannot compile %s statement (%s): %s\nExpecting code block, found: '%s'", 153 humannumbers.Ordinal(i+1), token.Condition, err.Error(), token.ParametersString(1+adjust)) 154 } 155 156 if byVal { 157 return token.Parameters[0], thenBlock, nil 158 } 159 160 caseIf, err := token.Block(0) 161 if err != nil { 162 return nil, nil, fmt.Errorf("cannot compile %s statement (%s): %s\nExpecting %s conditional block, found: '%s'", 163 humannumbers.Ordinal(i+1), token.Condition, err.Error(), token.Condition, token.ParametersString(0)) 164 } 165 return caseIf, thenBlock, nil 166 167 default: 168 return nil, nil, 169 fmt.Errorf("too many parameters for %s statement (%s)\nFound: '%s'\n%s", 170 humannumbers.Ordinal(i+1), token.Condition, token.ParametersStringAll(), errReferToDocs) 171 } 172 173 case "catch", "default": 174 switch token.ParametersLen() { 175 case 0: 176 return nil, nil, fmt.Errorf("missing parameters for %s statement (%s)\n%s", 177 humannumbers.Ordinal(i+1), token.Condition, errReferToDocs) 178 179 case 1: 180 thenBlock, err := token.Block(0) 181 if err != nil { 182 return nil, nil, 183 fmt.Errorf("cannot compile %s statement (%s): %s\nExpecting code block, found: '%s'", 184 humannumbers.Ordinal(i+1), token.Condition, err.Error(), token.ParametersString(0)) 185 } 186 return nil, thenBlock, nil 187 188 default: 189 return nil, nil, 190 fmt.Errorf("too many parameters for %s statement (%s)\nFound: '%s'\n%s", 191 humannumbers.Ordinal(i+1), token.Condition, token.ParametersStringAll(), errReferToDocs) 192 } 193 194 default: 195 return nil, nil, 196 fmt.Errorf("invalid %s statement '%s'", humannumbers.Ordinal(i+1), token.Condition) 197 } 198 } 199 200 func compareConditional(p *lang.Process, val string, caseIf []rune) (bool, error) { 201 if !types.IsBlockRune(caseIf) { 202 return val == string(caseIf), nil 203 } 204 205 fork := p.Fork(lang.F_PARENT_VARTABLE | lang.F_NO_STDIN | lang.F_CREATE_STDOUT | lang.F_NO_STDERR) 206 _, err := fork.Execute(caseIf) 207 if err != nil { 208 return false, err 209 } 210 211 b, err := fork.Stdout.ReadAll() 212 if err != nil { 213 return false, err 214 } 215 216 return val == string(utils.CrLfTrim(b)), err 217 } 218 219 func executeConditional(p *lang.Process, block []rune) (bool, error) { 220 fork := p.Fork(lang.F_PARENT_VARTABLE | lang.F_NO_STDIN | lang.F_CREATE_STDOUT | lang.F_NO_STDERR) 221 exitNum, err := fork.Execute(block) 222 if err != nil { 223 return false, err 224 } 225 226 b, err := fork.Stdout.ReadAll() 227 if err != nil { 228 return false, err 229 } 230 231 result := types.IsTrue(b, exitNum) 232 return result, nil 233 } 234 235 func executeThen(p *lang.Process, block []rune) error { 236 _, err := p.Fork(lang.F_PARENT_VARTABLE | lang.F_NO_STDIN).Execute(block) 237 return err 238 }