// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // All rights reserved. // builtin-string.go package expr import ( "fmt" "io" "strings" "git.portale-stac.it/go-pkg/expr/kern" ) const ( strParamOther = "other" ) // --- Start of function definitions func doJoinStr(funcName string, sep string, it kern.Iterator) (result any, err error) { var sb strings.Builder var v any for v, err = it.Next(); err == nil; v, err = it.Next() { if it.Index() > 0 { sb.WriteString(sep) } if s, ok := v.(string); ok { sb.WriteString(s) } else { err = kern.ErrExpectedGot(funcName, kern.TypeString, v) return } } if err == io.EOF { err = nil result = sb.String() } return } func joinStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { if sep, ok := args[kern.ParamSeparator].(string); ok { if v, exists := args[kern.ParamItem]; exists { argv := v.([]any) if len(argv) == 1 { if ls, ok := argv[0].(*kern.ListType); ok { result, err = doJoinStr(name, sep, NewListIterator(ls, nil)) } else if it, ok := argv[0].(kern.Iterator); ok { result, err = doJoinStr(name, sep, it) } else if s, ok := argv[0].(string); ok { result = s } else { err = kern.ErrInvalidParameterValue(name, kern.ParamItem, v) } } else { result, err = doJoinStr(name, sep, NewArrayIterator(argv)) } } } else { err = kern.ErrWrongParamType(name, kern.ParamSeparator, kern.TypeString, args[kern.ParamSeparator]) } return } func subStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { var start = 0 var count = -1 var source string var ok bool if source, ok = args[kern.ParamSource].(string); !ok { return nil, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource]) } if start, err = kern.ToGoInt(args[kern.ParamStart], name+"()"); err != nil { return } if count, err = kern.ToGoInt(args[kern.ParamCount], name+"()"); err != nil { return } if start < 0 { start = len(source) + start } if count < 0 { count = len(source) - start } end := min(start+count, len(source)) result = source[start:end] return } func trimStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { var source string var ok bool if source, ok = args[kern.ParamSource].(string); !ok { return nil, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource]) } result = strings.TrimSpace(source) return } func startsWithStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { var source, prefix string var ok bool result = false if source, ok = args[kern.ParamSource].(string); !ok { return result, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource]) } if prefix, ok = args[kern.ParamPrefix].(string); !ok { return result, kern.ErrWrongParamType(name, kern.ParamPrefix, kern.TypeString, args[kern.ParamPrefix]) } if strings.HasPrefix(source, prefix) { result = true } else if v, exists := args[strParamOther]; exists { argv := v.([]any) for i, targetSpec := range argv { if target, ok := targetSpec.(string); ok { if strings.HasPrefix(source, target) { result = true break } } else { err = fmt.Errorf("target item nr %d is %s, string expected", i+1, kern.TypeName(targetSpec)) break } } } return } func endsWithStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { var source, suffix string var ok bool result = false if source, ok = args[kern.ParamSource].(string); !ok { return result, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource]) } if suffix, ok = args[kern.ParamSuffix].(string); !ok { return result, kern.ErrWrongParamType(name, kern.ParamSuffix, kern.TypeString, args[kern.ParamSuffix]) } if strings.HasPrefix(source, suffix) { result = true } else if v, exists := args[strParamOther]; exists { argv := v.([]any) for i, targetSpec := range argv { if target, ok := targetSpec.(string); ok { if strings.HasSuffix(source, target) { result = true break } } else { err = fmt.Errorf("target item nr %d is %s, string expected", i+1, kern.TypeName(targetSpec)) break } } } return } func splitStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { var source, sep string var count int = -1 var parts []string var ok bool if source, ok = args[kern.ParamSource].(string); !ok { return result, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource]) } if sep, ok = args[kern.ParamSeparator].(string); !ok { return nil, fmt.Errorf("separator param must be string, got %s (%v)", kern.TypeName(args[kern.ParamSeparator]), args[kern.ParamSeparator]) } if count64, ok := args[kern.ParamCount].(int64); ok { // TODO replace type assertion with toInt() count = int(count64) } else { return nil, fmt.Errorf("part count must be integer, got %s (%v)", kern.TypeName(args[kern.ParamCount]), args[kern.ParamCount]) } if count > 0 { parts = strings.SplitN(source, sep, count) } else if count < 0 { parts = strings.Split(source, sep) } else { parts = []string{} } list := make(kern.ListType, len(parts)) for i, part := range parts { list[i] = part } result = &list return } func upperStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { if source, ok := args[kern.ParamSource].(string); ok { result = strings.ToUpper(source) } else { err = kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource]) } return } func lowerStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { if source, ok := args[kern.ParamSource].(string); ok { result = strings.ToLower(source) } else { err = kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource]) } return } // --- End of function definitions // Import above functions in the context func ImportStringFuncs(ctx kern.ExprContext) { ctx.RegisterFunc("strJoin", kern.NewGolangFunctor(joinStrFunc), kern.TypeString, []kern.ExprFuncParam{ NewFuncParam(kern.ParamSeparator), NewFuncParamFlag(kern.ParamItem, PfRepeat), }) ctx.RegisterFunc("strSub", kern.NewGolangFunctor(subStrFunc), kern.TypeString, []kern.ExprFuncParam{ NewFuncParam(kern.ParamSource), NewFuncParamFlagDef(kern.ParamStart, PfDefault, int64(0)), NewFuncParamFlagDef(kern.ParamCount, PfDefault, int64(-1)), }) ctx.RegisterFunc("strSplit", kern.NewGolangFunctor(splitStrFunc), "list of "+kern.TypeString, []kern.ExprFuncParam{ NewFuncParam(kern.ParamSource), NewFuncParamFlagDef(kern.ParamSeparator, PfDefault, ""), NewFuncParamFlagDef(kern.ParamCount, PfDefault, int64(-1)), }) ctx.RegisterFunc("strTrim", kern.NewGolangFunctor(trimStrFunc), kern.TypeString, []kern.ExprFuncParam{ NewFuncParam(kern.ParamSource), }) ctx.RegisterFunc("strStartsWith", kern.NewGolangFunctor(startsWithStrFunc), kern.TypeBoolean, []kern.ExprFuncParam{ NewFuncParam(kern.ParamSource), NewFuncParam(kern.ParamPrefix), NewFuncParamFlag(strParamOther, PfRepeat), }) ctx.RegisterFunc("strEndsWith", kern.NewGolangFunctor(endsWithStrFunc), kern.TypeBoolean, []kern.ExprFuncParam{ NewFuncParam(kern.ParamSource), NewFuncParam(kern.ParamSuffix), NewFuncParamFlag(strParamOther, PfRepeat), }) ctx.RegisterFunc("strUpper", kern.NewGolangFunctor(upperStrFunc), kern.TypeString, []kern.ExprFuncParam{ NewFuncParam(kern.ParamSource), }) ctx.RegisterFunc("strLower", kern.NewGolangFunctor(lowerStrFunc), kern.TypeString, []kern.ExprFuncParam{ NewFuncParam(kern.ParamSource), }) } // Register the import function in the import-register. // That will allow to import all function of this module by the "builtin" operator." func init() { RegisterBuiltinModule("string", ImportStringFuncs, "string utilities") }