package main import ( "reflect" "strconv" ) type NumMutator struct { Op NumOperator Var bool Value int } type NumOperator string const ( NumOperatorAdd NumOperator = "+" NumOperatorSub NumOperator = "-" NumOperatorMul NumOperator = "*" NumOperatorDiv NumOperator = "/" ) func NewNumOperatorFromString(str string) NumOperator { switch str { case "+": return NumOperatorAdd case "-": return NumOperatorSub case "*": return NumOperatorMul case "/": return NumOperatorDiv default: panic("invalid number operator: " + str) } } var numMutatorCache = map[string]interface{}{} func numMut2func[T int64 | float64](mutation string) (f func(value T, vars []string) T) { if mutation == "" { return func(value T, vars []string) T { return value } } // caching if v, ok := numMutatorCache[mutation]; ok { return v.(func(value T, vars []string) T) } defer func() { numMutatorCache[mutation] = f }() matches := numMutationPattern.FindAllStringSubmatch(mutation, -1) mutators := make([]NumMutator, 0, len(matches)) var err error for _, match := range matches { mut := NumMutator{Op: NewNumOperatorFromString(match[1])} if match[3] == "" { mut.Value, err = strconv.Atoi(match[2]) mut.Var = false if err != nil { panic("invalid number in number mutator: " + match[2]) } } else { mut.Var = true mut.Value, err = strconv.Atoi(match[3]) if err != nil { panic("invalid back reference group in number mutator: " + match[2]) } } mutators = append(mutators, mut) } numberParser := number_parser[T]() return func(value T, vars []string) T { for _, mutator := range mutators { var otherValue T if mutator.Var { other := numberParser(vars[mutator.Value]) otherValue = T(other) } else { otherValue = T(mutator.Value) } switch mutator.Op { case NumOperatorAdd: value += otherValue case NumOperatorSub: value -= otherValue case NumOperatorMul: value *= otherValue case NumOperatorDiv: value /= otherValue default: } } return value } } func number_parser[T int64 | float64]() func(str string) T { typeOfT := reflect.TypeOf(new(T)).Elem() typeOfInt64 := reflect.TypeOf(new(int64)).Elem() typeOfFloat64 := reflect.TypeOf(new(float64)).Elem() if typeOfT == typeOfInt64 { return func(str string) T { num, err := strconv.Atoi(str) if err != nil { panic("expected integer but found " + str) } return T(num) } } else if typeOfT == typeOfFloat64 { return func(str string) T { num, err := strconv.ParseFloat(str, 64) if err != nil { panic("expected float but found " + str) } return T(num) } } panic("invalid number type") }