mirror of
https://github.com/johnkerl/miller.git
synced 2026-07-25 08:53:55 +00:00
* JIT mlrval type-interfence: mlrval package * mlrmap refactor * complete merge from #779 * iterating * mlrval/format.go * mlrval/copy.go * bifs/arithmetic_test.go * iterate on bifs/collections_test.go * mlrval_cmp.go * mlrval JSON iterate * iterate applying mlrval refactors to dependent packages * first clean compile in a long while on this branch * results of first post-compile profiling * testing * bugfix in ofmt formatting * bugfix in octal-supporess * go fmt * neaten * regression tests all passing
350 lines
11 KiB
Go
350 lines
11 KiB
Go
package bifs
|
|
|
|
import (
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"github.com/johnkerl/miller/internal/pkg/lib"
|
|
"github.com/johnkerl/miller/internal/pkg/mlrval"
|
|
)
|
|
|
|
// ================================================================
|
|
func BIF_strlen(input1 *mlrval.Mlrval) *mlrval.Mlrval {
|
|
if !input1.IsStringOrVoid() {
|
|
return mlrval.ERROR
|
|
} else {
|
|
return mlrval.FromInt(int(utf8.RuneCountInString(input1.AcquireStringValue())))
|
|
}
|
|
}
|
|
|
|
// ================================================================
|
|
func BIF_string(input1 *mlrval.Mlrval) *mlrval.Mlrval {
|
|
return mlrval.FromString(input1.String())
|
|
}
|
|
|
|
// ================================================================
|
|
// Dot operator, with loose typecasting.
|
|
//
|
|
// For most operations, I don't like loose typecasting -- for example, in PHP
|
|
// "10" + 2 is the number 12 and in JavaScript it's the string "102", and I
|
|
// find both of those horrid and error-prone. In Miller, "10"+2 is MT_ERROR, by
|
|
// design, unless intentional casting is done like '$x=int("10")+2'.
|
|
//
|
|
// However, for dotting, in practice I tipped over and allowed dotting of
|
|
// strings and ints: so while "10" + 2 is an error in Miller, '"10". 2' is
|
|
// "102". Unlike with "+", with "." there is no ambiguity about what the output
|
|
// should be: always the string concatenation of the string representations of
|
|
// the two arguments. So, we do the string-cast for the user.
|
|
|
|
func dot_s_xx(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
|
|
return mlrval.FromString(input1.String() + input2.String())
|
|
}
|
|
|
|
var dot_dispositions = [mlrval.MT_DIM][mlrval.MT_DIM]BinaryFunc{
|
|
// . ERROR ABSENT NULL VOID STRING INT FLOAT BOOL ARRAY MAP FUNC
|
|
/*ERROR */ {_erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _absn, _absn, _erro},
|
|
/*ABSENT */ {_erro, _absn, _null, _void, _2___, _s2__, _s2__, _s2__, _absn, _absn, _erro},
|
|
/*NULL */ {_erro, _null, _null, _void, _2___, _s2__, _s2__, _s2__, _absn, _absn, _erro},
|
|
/*VOID */ {_erro, _void, _void, _void, _2___, _s2__, _s2__, _s2__, _absn, _absn, _erro},
|
|
/*STRING */ {_erro, _1___, _1___, _1___, dot_s_xx, dot_s_xx, dot_s_xx, dot_s_xx, _erro, _erro, _erro},
|
|
/*INT */ {_erro, _s1__, _1___, _s1__, dot_s_xx, dot_s_xx, dot_s_xx, dot_s_xx, _erro, _erro, _erro},
|
|
/*FLOAT */ {_erro, _s1__, _1___, _s1__, dot_s_xx, dot_s_xx, dot_s_xx, dot_s_xx, _erro, _erro, _erro},
|
|
/*BOOL */ {_erro, _s1__, _1___, _s1__, dot_s_xx, dot_s_xx, dot_s_xx, dot_s_xx, _erro, _erro, _erro},
|
|
/*ARRAY */ {_erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro},
|
|
/*MAP */ {_erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro},
|
|
/*FUNC */ {_erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro},
|
|
}
|
|
|
|
func BIF_dot(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
|
|
return dot_dispositions[input1.Type()][input2.Type()](input1, input2)
|
|
}
|
|
|
|
// ================================================================
|
|
// substr1(s,m,n) gives substring of s from 1-up position m to n inclusive.
|
|
// Negative indices -len .. -1 alias to 0 .. len-1.
|
|
|
|
func BIF_substr_1_up(input1, input2, input3 *mlrval.Mlrval) *mlrval.Mlrval {
|
|
if !input1.IsStringOrVoid() {
|
|
return mlrval.ERROR
|
|
}
|
|
|
|
// Handle UTF-8 correctly: len(input1.AcquireStringValue()) will count bytes, not runes.
|
|
runes := []rune(input1.AcquireStringValue())
|
|
strlen := int(len(runes))
|
|
|
|
// For array slices like s[1:2], s[:2], s[1:], when the lower index is
|
|
// empty in the DSL expression it comes in here as a 1. But when the upper
|
|
// index is empty in the DSL expression it comes in here as "".
|
|
if !input2.IsInt() {
|
|
return mlrval.ERROR
|
|
}
|
|
lowerMindex := input2.AcquireIntValue()
|
|
|
|
upperMindex := strlen
|
|
if input3.IsVoid() {
|
|
// Keep strlen
|
|
} else if !input3.IsInt() {
|
|
return mlrval.ERROR
|
|
} else {
|
|
upperMindex = input3.AcquireIntValue()
|
|
}
|
|
|
|
// Convert from negative-aliased 1-up to positive-only 0-up
|
|
m, mok := unaliasArrayLengthIndex(strlen, lowerMindex)
|
|
n, nok := unaliasArrayLengthIndex(strlen, upperMindex)
|
|
|
|
if !mok || !nok {
|
|
return mlrval.VOID
|
|
} else if m > n {
|
|
return mlrval.VOID
|
|
} else {
|
|
// Note Golang slice indices are 0-up, and the 1st index is inclusive
|
|
// while the 2nd is exclusive. For Miller, indices are 1-up and both
|
|
// are inclusive.
|
|
return mlrval.FromString(string(runes[m : n+1]))
|
|
}
|
|
}
|
|
|
|
// ================================================================
|
|
// substr0(s,m,n) gives substring of s from 0-up position m to n inclusive.
|
|
// Negative indices -len .. -1 alias to 0 .. len-1.
|
|
|
|
func BIF_substr_0_up(input1, input2, input3 *mlrval.Mlrval) *mlrval.Mlrval {
|
|
if !input1.IsStringOrVoid() {
|
|
return mlrval.ERROR
|
|
}
|
|
|
|
// Handle UTF-8 correctly: len(input1.AcquireStringValue()) will count bytes, not runes.
|
|
runes := []rune(input1.AcquireStringValue())
|
|
strlen := int(len(runes))
|
|
|
|
// For array slices like s[1:2], s[:2], s[1:], when the lower index is
|
|
// empty in the DSL expression it comes in here as a 1. But when the upper
|
|
// index is empty in the DSL expression it comes in here as "".
|
|
if !input2.IsInt() {
|
|
return mlrval.ERROR
|
|
}
|
|
lowerMindex := input2.AcquireIntValue()
|
|
if lowerMindex >= 0 {
|
|
// Make 1-up
|
|
lowerMindex += 1
|
|
}
|
|
|
|
upperMindex := strlen
|
|
if input3.IsVoid() {
|
|
// Keep strlen
|
|
} else if !input3.IsInt() {
|
|
return mlrval.ERROR
|
|
} else {
|
|
upperMindex = input3.AcquireIntValue()
|
|
if upperMindex >= 0 {
|
|
// Make 1-up
|
|
upperMindex += 1
|
|
}
|
|
}
|
|
|
|
// Convert from negative-aliased 1-up to positive-only 0-up
|
|
m, mok := unaliasArrayLengthIndex(strlen, lowerMindex)
|
|
n, nok := unaliasArrayLengthIndex(strlen, upperMindex)
|
|
|
|
if !mok || !nok {
|
|
return mlrval.VOID
|
|
} else if m > n {
|
|
return mlrval.VOID
|
|
} else {
|
|
// Note Golang slice indices are 0-up, and the 1st index is inclusive
|
|
// while the 2nd is exclusive. For Miller, indices are 1-up and both
|
|
// are inclusive.
|
|
return mlrval.FromString(string(runes[m : n+1]))
|
|
}
|
|
}
|
|
|
|
// ================================================================
|
|
func BIF_truncate(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
|
|
if input1.IsErrorOrAbsent() {
|
|
return input1
|
|
}
|
|
if input2.IsErrorOrAbsent() {
|
|
return input2
|
|
}
|
|
if !input1.IsStringOrVoid() {
|
|
return mlrval.ERROR
|
|
}
|
|
if !input2.IsInt() {
|
|
return mlrval.ERROR
|
|
}
|
|
if input2.AcquireIntValue() < 0 {
|
|
return mlrval.ERROR
|
|
}
|
|
|
|
// Handle UTF-8 correctly: len(input1.AcquireStringValue()) will count bytes, not runes.
|
|
runes := []rune(input1.AcquireStringValue())
|
|
oldLength := int(len(runes))
|
|
maxLength := input2.AcquireIntValue()
|
|
if oldLength <= maxLength {
|
|
return input1
|
|
} else {
|
|
return mlrval.FromString(string(runes[0:maxLength]))
|
|
}
|
|
}
|
|
|
|
// ================================================================
|
|
func BIF_lstrip(input1 *mlrval.Mlrval) *mlrval.Mlrval {
|
|
if input1.IsString() {
|
|
return mlrval.FromString(strings.TrimLeft(input1.AcquireStringValue(), " \t"))
|
|
} else {
|
|
return input1
|
|
}
|
|
}
|
|
|
|
func BIF_rstrip(input1 *mlrval.Mlrval) *mlrval.Mlrval {
|
|
if input1.IsString() {
|
|
return mlrval.FromString(strings.TrimRight(input1.AcquireStringValue(), " \t"))
|
|
} else {
|
|
return input1
|
|
}
|
|
}
|
|
|
|
func BIF_strip(input1 *mlrval.Mlrval) *mlrval.Mlrval {
|
|
if input1.IsString() {
|
|
return mlrval.FromString(strings.Trim(input1.AcquireStringValue(), " \t"))
|
|
} else {
|
|
return input1
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
func BIF_collapse_whitespace(input1 *mlrval.Mlrval) *mlrval.Mlrval {
|
|
return BIF_collapse_whitespace_regexp(input1, WhitespaceRegexp())
|
|
}
|
|
|
|
func BIF_collapse_whitespace_regexp(input1 *mlrval.Mlrval, whitespaceRegexp *regexp.Regexp) *mlrval.Mlrval {
|
|
if input1.IsString() {
|
|
return mlrval.FromString(whitespaceRegexp.ReplaceAllString(input1.AcquireStringValue(), " "))
|
|
} else {
|
|
return input1
|
|
}
|
|
}
|
|
|
|
func WhitespaceRegexp() *regexp.Regexp {
|
|
return regexp.MustCompile("\\s+")
|
|
}
|
|
|
|
// ================================================================
|
|
func BIF_toupper(input1 *mlrval.Mlrval) *mlrval.Mlrval {
|
|
if input1.IsString() {
|
|
return mlrval.FromString(strings.ToUpper(input1.AcquireStringValue()))
|
|
} else if input1.IsVoid() {
|
|
return input1
|
|
} else {
|
|
return input1
|
|
}
|
|
}
|
|
|
|
func BIF_tolower(input1 *mlrval.Mlrval) *mlrval.Mlrval {
|
|
if input1.IsString() {
|
|
return mlrval.FromString(strings.ToLower(input1.AcquireStringValue()))
|
|
} else if input1.IsVoid() {
|
|
return input1
|
|
} else {
|
|
return input1
|
|
}
|
|
}
|
|
|
|
func BIF_capitalize(input1 *mlrval.Mlrval) *mlrval.Mlrval {
|
|
if input1.IsString() {
|
|
if input1.AcquireStringValue() == "" {
|
|
return input1
|
|
} else {
|
|
runes := []rune(input1.AcquireStringValue())
|
|
rfirst := runes[0]
|
|
rrest := runes[1:]
|
|
sfirst := strings.ToUpper(string(rfirst))
|
|
srest := string(rrest)
|
|
return mlrval.FromString(sfirst + srest)
|
|
}
|
|
} else {
|
|
return input1
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
func BIF_clean_whitespace(input1 *mlrval.Mlrval) *mlrval.Mlrval {
|
|
return BIF_strip(
|
|
BIF_collapse_whitespace_regexp(
|
|
input1, WhitespaceRegexp(),
|
|
),
|
|
)
|
|
}
|
|
|
|
// ================================================================
|
|
func BIF_hexfmt(input1 *mlrval.Mlrval) *mlrval.Mlrval {
|
|
if input1.IsInt() {
|
|
return mlrval.FromString("0x" + strconv.FormatUint(uint64(input1.AcquireIntValue()), 16))
|
|
} else {
|
|
return input1
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
func fmtnum_is(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
|
|
if !input2.IsString() {
|
|
return mlrval.ERROR
|
|
}
|
|
formatString := input2.AcquireStringValue()
|
|
formatter, err := mlrval.GetFormatter(formatString)
|
|
if err != nil {
|
|
return mlrval.ERROR
|
|
}
|
|
|
|
return formatter.Format(input1)
|
|
}
|
|
|
|
func fmtnum_fs(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
|
|
if !input2.IsString() {
|
|
return mlrval.ERROR
|
|
}
|
|
formatString := input2.AcquireStringValue()
|
|
formatter, err := mlrval.GetFormatter(formatString)
|
|
if err != nil {
|
|
return mlrval.ERROR
|
|
}
|
|
|
|
return formatter.Format(input1)
|
|
}
|
|
|
|
func fmtnum_bs(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
|
|
if !input2.IsString() {
|
|
return mlrval.ERROR
|
|
}
|
|
formatString := input2.AcquireStringValue()
|
|
formatter, err := mlrval.GetFormatter(formatString)
|
|
if err != nil {
|
|
return mlrval.ERROR
|
|
}
|
|
|
|
intMv := mlrval.FromInt(lib.BoolToInt(input1.AcquireBoolValue()))
|
|
|
|
return formatter.Format(intMv)
|
|
}
|
|
|
|
var fmtnum_dispositions = [mlrval.MT_DIM][mlrval.MT_DIM]BinaryFunc{
|
|
// . ERROR ABSENT NULL VOID STRING INT FLOAT BOOL ARRAY MAP FUNC
|
|
/*ERROR */ {_erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro},
|
|
/*ABSENT */ {_erro, _absn, _absn, _absn, _absn, _absn, _absn, _erro, _erro, _erro, _erro},
|
|
/*NULL */ {_erro, _absn, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro},
|
|
/*VOID */ {_erro, _absn, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro},
|
|
/*STRING */ {_erro, _absn, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro},
|
|
/*INT */ {_erro, _absn, _erro, _erro, fmtnum_is, _erro, _erro, _erro, _erro, _erro, _erro},
|
|
/*FLOAT */ {_erro, _absn, _erro, _erro, fmtnum_fs, _erro, _erro, _erro, _erro, _erro, _erro},
|
|
/*BOOL */ {_erro, _absn, _erro, _erro, fmtnum_bs, _erro, _erro, _erro, _erro, _erro, _erro},
|
|
/*ARRAY */ {_erro, _absn, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro},
|
|
/*MAP */ {_erro, _absn, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro},
|
|
/*FUNC */ {_erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro, _erro},
|
|
}
|
|
|
|
func BIF_fmtnum(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
|
|
return fmtnum_dispositions[input1.Type()][input2.Type()](input1, input2)
|
|
}
|