Skip to content

Commit

Permalink
Support thousands separator in fmtnum
Browse files Browse the repository at this point in the history
  • Loading branch information
johnkerl committed Feb 18, 2024
1 parent f5eaf29 commit 02933ed
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 4 deletions.
9 changes: 7 additions & 2 deletions pkg/dsl/cst/builtin_function_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -2000,10 +2000,15 @@ Note that NaN has the property that NaN != NaN, so you need 'is_nan(x)' rather t
name: "fmtnum",
class: FUNC_CLASS_CONVERSION,
help: `Convert int/float/bool to string using printf-style format string (https://pkg.go.dev/fmt), e.g.
'$s = fmtnum($n, "%08d")' or '$t = fmtnum($n, "%.6e")'. This function recurses on array and map values.`,
'$s = fmtnum($n, "%08d")' or '$t = fmtnum($n, "%.6e")'. Miller-specific extension: "%_d" and "%_f" for comma-separated thousands. This function recurses on array and map values.`,
binaryFunc: bifs.BIF_fmtnum,
examples: []string{
`$x = fmtnum($x, "%.6f")`,
`$y = fmtnum($x, "%.6f")`,
`$o = fmtnum($n, "%d")`,
`$o = fmtnum($n, "%12d")`,
`$y = fmtnum($x, "%.6_f")`,
`$o = fmtnum($n, "%_d")`,
`$o = fmtnum($n, "%12_d")`,
},
},

Expand Down
93 changes: 91 additions & 2 deletions pkg/mlrval/mlrval_format.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ package mlrval

import (
"fmt"
"os"
"strconv"
"strings"

"golang.org/x/text/language"
"golang.org/x/text/message"
)

//----------------------------------------------------------------
Expand Down Expand Up @@ -103,16 +107,26 @@ func newFormatter(
goFormatString = strings.ReplaceAll(goFormatString, "le", "e")
goFormatString = strings.ReplaceAll(goFormatString, "lg", "g")

// MIller 5 and below required C format strings compatible with 64-bit ints
// Miller 5 and below required C format strings compatible with 64-bit ints
// and double-precision floats: e.g. "%08lld" and "%9.6lf". For Miller 6,
// We must still accept these for backward compatibility.
// we must still accept these for backward compatibility.
if strings.HasSuffix(goFormatString, "_d") {
// Special sub-case of "d"; must be checked first
n := len(goFormatString)
return newFormatterToSeparatedInt(goFormatString[:n-2] + "d"), nil
}
if strings.HasSuffix(goFormatString, "d") {
return newFormatterToInt(goFormatString), nil
}
if strings.HasSuffix(goFormatString, "x") {
return newFormatterToInt(goFormatString), nil
}

if strings.HasSuffix(goFormatString, "_f") {
// Special sub-case of "f"; must be checked first
n := len(goFormatString)
return newFormatterToSeparatedFloat(goFormatString[:n-2] + "f"), nil
}
if strings.HasSuffix(goFormatString, "f") {
return newFormatterToFloat(goFormatString), nil
}
Expand Down Expand Up @@ -164,6 +178,81 @@ func (formatter *formatterToFloat) FormatFloat(floatValue float64) string {

// ----------------------------------------------------------------

func getLanguageTag() language.Tag {
v, ok := os.LookupEnv("LANG")
if ok {
return language.Make(v)
} else {
return language.Make("en")
}
}

// ----------------------------------------------------------------

type formatterToSeparatedInt struct {
goFormatString string
printer *message.Printer
}

func newFormatterToSeparatedInt(goFormatString string) IFormatter {
return &formatterToSeparatedInt{
goFormatString: goFormatString,
printer: message.NewPrinter(getLanguageTag()),
}
}

func (formatter *formatterToSeparatedInt) Format(mv *Mlrval) *Mlrval {
intValue, isInt := mv.GetIntValue()
if isInt {
formatted := formatter.printer.Sprintf(formatter.goFormatString, intValue)
return TryFromIntString(formatted)
}
floatValue, isFloat := mv.GetFloatValue()
if isFloat {
formatted := formatter.printer.Sprintf(formatter.goFormatString, int(floatValue))
return TryFromIntString(formatted)
}
return mv
}

func (formatter *formatterToSeparatedInt) FormatFloat(floatValue float64) string {
return formatter.printer.Sprintf(formatter.goFormatString, int(floatValue))
}

// ----------------------------------------------------------------

type formatterToSeparatedFloat struct {
goFormatString string
printer *message.Printer
}

func newFormatterToSeparatedFloat(goFormatString string) IFormatter {
return &formatterToSeparatedFloat{
goFormatString: goFormatString,
printer: message.NewPrinter(getLanguageTag()),
}
}

func (formatter *formatterToSeparatedFloat) Format(mv *Mlrval) *Mlrval {
floatValue, isFloat := mv.GetFloatValue()
if isFloat {
formatted := formatter.printer.Sprintf(formatter.goFormatString, floatValue)
return TryFromFloatString(formatted)
}
intValue, isInt := mv.GetIntValue()
if isInt {
formatted := formatter.printer.Sprintf(formatter.goFormatString, float64(intValue))
return TryFromFloatString(formatted)
}
return mv
}

func (formatter *formatterToSeparatedFloat) FormatFloat(floatValue float64) string {
return formatter.printer.Sprintf(formatter.goFormatString, floatValue)
}

// ----------------------------------------------------------------

type formatterToInt struct {
goFormatString string
}
Expand Down

0 comments on commit 02933ed

Please sign in to comment.