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  }