From 9cc58fa3b53b5065b8bb04f14414da371329f8f7 Mon Sep 17 00:00:00 2001 From: John Kerl Date: Mon, 15 Feb 2021 00:55:40 -0500 Subject: [PATCH] Port regextract DSL function from C to Go --- go/src/dsl/cst/builtin_function_manager.go | 16 +++++++- go/src/lib/regex.go | 12 +++++- go/src/transformers/having-fields.go | 2 +- go/src/types/mlrval_functions_regex.go | 44 ++++++++++++++++++++-- go/todo.txt | 1 - 5 files changed, 66 insertions(+), 9 deletions(-) diff --git a/go/src/dsl/cst/builtin_function_manager.go b/go/src/dsl/cst/builtin_function_manager.go index 7f800338f..1ba95363d 100644 --- a/go/src/dsl/cst/builtin_function_manager.go +++ b/go/src/dsl/cst/builtin_function_manager.go @@ -335,8 +335,6 @@ regex-match operator: try '$y = ~$x'.`, // FUNC_CLASS_STRING // TODO: - // regextract : help: `Example: '$name=regextract($name, "[A-Z]{3}[0-9]{2}")'`, - // regextract_or_else : help: `Example: '$name=regextract_or_else($name, "[A-Z]{3}[0-9]{2}", "default")'`, // system : help: `Run command string, yielding its stdout minus final carriage return. { @@ -381,6 +379,20 @@ regex-match operator: try '$y = ~$x'.`, unaryFunc: types.MlrvalLStrip, }, + { + name: "regextract", + class: FUNC_CLASS_STRING, + help: `Example: '$name=regextract($name, "[A-Z]{3}[0-9]{2}")'`, + binaryFunc: types.MlrvalRegextract, + }, + + { + name: "regextract_or_else", + class: FUNC_CLASS_STRING, + help: `Example: '$name=regextract_or_else($name, "[A-Z]{3}[0-9]{2}", "default")'`, + ternaryFunc: types.MlrvalRegextractOrElse, + }, + { name: "rstrip", class: FUNC_CLASS_STRING, diff --git a/go/src/lib/regex.go b/go/src/lib/regex.go index 8c9d5ddf8..a05dfa5d2 100644 --- a/go/src/lib/regex.go +++ b/go/src/lib/regex.go @@ -3,6 +3,7 @@ package lib import ( "errors" "fmt" + "os" "regexp" "strings" ) @@ -14,7 +15,7 @@ import ( // * If the regex_string is of the form a.*b, compiles it using cflags without REG_ICASE. // * If the regex_string is of the form "a.*b", compiles a.*b using cflags without REG_ICASE. // * If the regex_string is of the form "a.*b"i, compiles a.*b using cflags with REG_ICASE. -func CompilerMillerRegex(regexString string) (*regexp.Regexp, error) { +func CompileMillerRegex(regexString string) (*regexp.Regexp, error) { if !strings.HasPrefix(regexString, "\"") { return regexp.Compile(regexString) } else { @@ -41,3 +42,12 @@ func CompilerMillerRegex(regexString string) (*regexp.Regexp, error) { } } } + +func CompileMillerRegexOrDie(regexString string) *regexp.Regexp { + regex, err := CompileMillerRegex(regexString) + if err != nil { + fmt.Fprint(os.Stderr, err) + os.Exit(1) + } + return regex +} diff --git a/go/src/transformers/having-fields.go b/go/src/transformers/having-fields.go index 68f6500a4..290119b81 100644 --- a/go/src/transformers/having-fields.go +++ b/go/src/transformers/having-fields.go @@ -180,7 +180,7 @@ func NewTransformerHavingFields( } else { // Let them type in a.*b if they want, or "a.*b", or "a.*b"i. // Strip off the leading " and trailing " or "i. - regex, err := lib.CompilerMillerRegex(regexString) + regex, err := lib.CompileMillerRegex(regexString) if err != nil { fmt.Fprintf( os.Stderr, diff --git a/go/src/types/mlrval_functions_regex.go b/go/src/types/mlrval_functions_regex.go index ae4e273d6..5a850805e 100644 --- a/go/src/types/mlrval_functions_regex.go +++ b/go/src/types/mlrval_functions_regex.go @@ -1,8 +1,9 @@ package types import ( - "regexp" "strings" + + "miller/src/lib" ) // ================================================================ @@ -53,7 +54,7 @@ func MlrvalSub(ma, mb, mc *Mlrval) Mlrval { return MlrvalFromError() } // TODO: better exception-handling - re := regexp.MustCompile(mb.printrep) + re := lib.CompileMillerRegexOrDie(mb.printrep) onFirst := true output := re.ReplaceAllStringFunc(ma.printrep, func(s string) string { @@ -90,7 +91,7 @@ func MlrvalGsub(ma, mb, mc *Mlrval) Mlrval { return MlrvalFromError() } // TODO: better exception-handling - re := regexp.MustCompile(mb.printrep) + re := lib.CompileMillerRegexOrDie(mb.printrep) return MlrvalFromString( re.ReplaceAllString(ma.printrep, mc.printrep), ) @@ -113,7 +114,7 @@ func MlrvalStringMatchesRegexp(ma, mb *Mlrval) Mlrval { return MlrvalFromError() } // TODO: better exception-handling - re := regexp.MustCompile(mb.printrep) + re := lib.CompileMillerRegexOrDie(mb.printrep) return MlrvalFromBool( re.MatchString(ma.printrep), ) @@ -127,3 +128,38 @@ func MlrvalStringDoesNotMatchRegexp(ma, mb *Mlrval) Mlrval { return matches // error, absent, etc. } } + +// TODO: find a way to keep and stash a precompiled regex, somewhere in the CST ... +func MlrvalRegextract(ma, mb *Mlrval) Mlrval { + if !ma.IsString() { + return MlrvalFromError() + } + if !mb.IsString() { + return MlrvalFromError() + } + regex := lib.CompileMillerRegexOrDie(mb.printrep) + // TODO: See if we need FindStringIndex or FindStringSubmatch to distinguish from matching "". + output := regex.FindString(ma.printrep) + if output != "" { + return MlrvalFromString(output) + } else { + return MlrvalFromAbsent() + } +} + +func MlrvalRegextractOrElse(ma, mb, mc *Mlrval) Mlrval { + if !ma.IsString() { + return MlrvalFromError() + } + if !mb.IsString() { + return MlrvalFromError() + } + regex := lib.CompileMillerRegexOrDie(mb.printrep) + // TODO: See if we need FindStringIndex or FindStringSubmatch to distinguish from matching "". + output := regex.FindString(ma.printrep) + if output != "" { + return MlrvalFromString(output) + } else { + return *mc + } +} diff --git a/go/todo.txt b/go/todo.txt index 89d663122..887c89989 100644 --- a/go/todo.txt +++ b/go/todo.txt @@ -118,7 +118,6 @@ no need to bootstrap a parser for the parser-generator language * stats1: o actualize -S and -F -- ? o regexed - - needs implementation of "a.*b"i - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ! JSON null needs to be passed through as-is ... somehow ... new mlrval type??