From 7e85497ec404b526492a347e6879398fa3554dcf Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Sat, 22 Mar 2025 01:43:58 +0100 Subject: [PATCH] Implement lua engine for the evaluations --- .gitignore | 2 + go.mod | 2 + go.sum | 2 + main.go | 237 ++++++++++++++++++++++++++++++++++++++++------------- 4 files changed, 188 insertions(+), 55 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..13fc1fc --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.exe +*.xml diff --git a/go.mod b/go.mod index 0fba53a..93792d9 100644 --- a/go.mod +++ b/go.mod @@ -3,3 +3,5 @@ module modify go 1.24.1 require github.com/Knetic/govaluate v3.0.0+incompatible + +require github.com/yuin/gopher-lua v1.1.1 // indirect diff --git a/go.sum b/go.sum index bb59ab5..3c3dbba 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg= github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= diff --git a/main.go b/main.go index fd5621a..862ea9d 100644 --- a/main.go +++ b/main.go @@ -8,9 +8,10 @@ import ( "os" "path/filepath" "regexp" + "strconv" "strings" - "github.com/Knetic/govaluate" + lua "github.com/yuin/gopher-lua" ) var Error *log.Logger @@ -30,67 +31,193 @@ func init() { } func main() { + // Define flags + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "Usage: %s <...files>\n", os.Args[0]) + fmt.Fprintf(os.Stderr, "Example: %s \"(\\d+),(\\d+)\" \"v[1] * 1.5 * v[2]\" data.xml\n", os.Args[0]) + fmt.Fprintf(os.Stderr, " or simplified: %s \"(\\d+),(\\d+)\" \"v1 * 1.5 * v2\" data.xml\n", os.Args[0]) + fmt.Fprintf(os.Stderr, " or even simpler: %s \"(\\d+)\" \"*1.5\" data.xml\n", os.Args[0]) + fmt.Fprintf(os.Stderr, "\nNote: v1, v2, etc. are shortcuts for v[1], v[2], etc.\n") + fmt.Fprintf(os.Stderr, " If expression starts with an operator like *, /, +, -, etc., v1 is automatically prepended\n") + } + flag.Parse() args := flag.Args() - if len(args) == 0 { - Error.Println("No files provided") - log.Printf("Usage: %s <...files>", os.Args[0]) - log.Printf("Example: %s go run . \"(\\d+)\" \"/10\" Vehicle_JLTV.xml", os.Args[0]) - log.Printf("Note: Field must include valid regex with one capture group") - return - } - field := args[0] - operation := args[1] - files := args[2:] - if field == "" || operation == "" || len(files) == 0 { - Error.Println("Invalid arguments") - log.Printf("Usage: %s <...files>", os.Args[0]) - return - } - log.Printf("Field: %s", field) - log.Printf("Operation: %s", operation) - log.Printf("Processing files: %v", files) - fieldRegex, err := regexp.Compile(field) - if err != nil { - Error.Printf("Error compiling field regex: %v", err) + if len(args) < 3 { + Error.Println("Insufficient arguments") + flag.Usage() return } + regexPattern := args[0] + luaExpr := args[1] + files := args[2:] + + // Check if the expression needs v1 to be prepended + if strings.HasPrefix(luaExpr, "*") || strings.HasPrefix(luaExpr, "/") || + strings.HasPrefix(luaExpr, "+") || strings.HasPrefix(luaExpr, "-") || + strings.HasPrefix(luaExpr, "^") || strings.HasPrefix(luaExpr, "%") { + luaExpr = "v[1]" + luaExpr + log.Printf("Expression modified to: %s", luaExpr) + } + + // Replace shorthand v1, v2, etc. with v[1], v[2] + shorthandRegex := regexp.MustCompile(`\bv(\d+)\b`) + luaExpr = shorthandRegex.ReplaceAllString(luaExpr, "v[$1]") + log.Printf("Final expression: %s", luaExpr) + + // Check if the expression is a simple expression or explicitly manipulates v table + var fullScript string + // Add custom script header + scriptHeader := ` +-- Custom Lua helpers for math operations +function min(a, b) return math.min(a, b) end +function max(a, b) return math.max(a, b) end +function round(x) return math.floor(x + 0.5) end +function floor(x) return math.floor(x) end +function ceil(x) return math.ceil(x) end +` + + if strings.Contains(luaExpr, "v[1] =") || strings.Contains(luaExpr, "v[2] =") || + strings.Contains(luaExpr, "return") { + // Already has assignments, use as is + fullScript = fmt.Sprintf(`%s +function process(v) + %s + return v +end +`, scriptHeader, luaExpr) + } else { + // Simple expression, capture the result and assign to v[1] + fullScript = fmt.Sprintf(`%s +function process(v) + local result = %s + v[1] = result + return v +end +`, scriptHeader, luaExpr) + } + + log.Printf("Regex pattern: %s", regexPattern) + log.Printf("Lua script: %s", fullScript) + log.Printf("Processing files: %v", files) + + // Compile the pattern for file processing + pattern, err := regexp.Compile(regexPattern) + if err != nil { + Error.Printf("Invalid regex pattern: %v", err) + return + } + + // Process each file for _, file := range files { - log.Printf("Processing file: %s", file) - fullPath := filepath.Join(".", file) - content, err := os.ReadFile(fullPath) + err := processFile(file, pattern, fullScript) if err != nil { - Error.Printf("Error reading file: %v", err) - continue + Error.Printf("Error processing file %s: %v", file, err) } - lines := strings.Split(string(content), "\n") - for i, line := range lines { - matches := fieldRegex.FindStringSubmatch(line) - if len(matches) > 1 { - log.Printf("Line: %s", line) - oldValue := matches[1] - expression, err := govaluate.NewEvaluableExpression(oldValue + operation) - if err != nil { - Error.Printf("Error creating expression: %v", err) - continue - } - parameters := map[string]interface{}{"oldValue": oldValue} - newValue, err := expression.Evaluate(parameters) - if err != nil { - Error.Printf("Error evaluating expression: %v", err) - continue - } - updatedLine := strings.Replace(line, oldValue, fmt.Sprintf("%v", newValue), 1) - log.Printf("Updated line: %s", updatedLine) - lines[i] = updatedLine - } - } - err = os.WriteFile(fullPath, []byte(strings.Join(lines, "\n")), 0644) - if err != nil { - Error.Printf("Error writing file: %v", err) - continue - } - log.Printf("File %s updated successfully", file) } } + +func processFile(filename string, pattern *regexp.Regexp, luaScript string) error { + log.Printf("Processing file: %s", filename) + fullPath := filepath.Join(".", filename) + + content, err := os.ReadFile(fullPath) + if err != nil { + return fmt.Errorf("error reading file: %v", err) + } + + fileContent := string(content) + modified := false + + // Initialize Lua state once per file + L := lua.NewState() + defer L.Close() + + // Load math library + L.Push(L.GetGlobal("require")) + L.Push(lua.LString("math")) + if err := L.PCall(1, 1, nil); err != nil { + return fmt.Errorf("error loading Lua math library: %v", err) + } + + // Load the Lua script + if err := L.DoString(luaScript); err != nil { + return fmt.Errorf("error in Lua script: %v", err) + } + + // Process all regex matches + result := pattern.ReplaceAllStringFunc(fileContent, func(match string) string { + captures := pattern.FindStringSubmatch(match) + if len(captures) <= 1 { + // No capture groups, return unchanged + return match + } + + // Create a Lua table with the captured values + vTable := L.NewTable() + for i, capture := range captures[1:] { + // Convert each capture to float if possible + floatVal, err := strconv.ParseFloat(capture, 64) + if err == nil { + L.RawSetInt(vTable, i+1, lua.LNumber(floatVal)) + } else { + L.RawSetInt(vTable, i+1, lua.LString(capture)) + } + } + + // Call the Lua function with the table + L.Push(L.GetGlobal("process")) + L.Push(vTable) + if err := L.PCall(1, 1, nil); err != nil { + Error.Printf("Error executing Lua script: %v", err) + return match // Return unchanged on error + } + + // Get the result table + resultTable := L.Get(-1) + L.Pop(1) + + if tbl, ok := resultTable.(*lua.LTable); ok { + // Prepare to replace each capture group + result := match + for i := 1; i <= len(captures)-1; i++ { + oldVal := captures[i] + + // Get new value from Lua + luaVal := tbl.RawGetInt(i) + var newVal string + + switch v := luaVal.(type) { + case lua.LNumber: + newVal = strconv.FormatFloat(float64(v), 'f', -1, 64) + case lua.LString: + newVal = string(v) + default: + newVal = fmt.Sprintf("%v", v) + } + + // Replace old value with new value + result = strings.Replace(result, oldVal, newVal, 1) + } + modified = true + return result + } + + // If something went wrong, return the original match + return match + }) + + // Only write if changes were made + if modified { + err = os.WriteFile(fullPath, []byte(result), 0644) + if err != nil { + return fmt.Errorf("error writing file: %v", err) + } + log.Printf("File %s updated successfully", filename) + } else { + log.Printf("No changes made to %s", filename) + } + + return nil +}