diff --git a/pkg/yqlib/expression_processing_test.go b/pkg/yqlib/expression_processing_test.go index 0af22b9f..3c4d510d 100644 --- a/pkg/yqlib/expression_processing_test.go +++ b/pkg/yqlib/expression_processing_test.go @@ -15,6 +15,16 @@ var pathTests = []struct { expectedTokens []interface{} expectedPostFix []interface{} }{ + { + "0x12", + append(make([]interface{}, 0), "18 (int64)"), + append(make([]interface{}, 0), "18 (int64)"), + }, + { + "0X12", + append(make([]interface{}, 0), "18 (int64)"), + append(make([]interface{}, 0), "18 (int64)"), + }, { ".a\n", append(make([]interface{}, 0), "a"), diff --git a/pkg/yqlib/expression_tokeniser.go b/pkg/yqlib/expression_tokeniser.go index df93cdf8..f3e81be7 100644 --- a/pkg/yqlib/expression_tokeniser.go +++ b/pkg/yqlib/expression_tokeniser.go @@ -166,6 +166,20 @@ func numberValue() lex.Action { } } +func hexValue() lex.Action { + return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { + var originalString = string(m.Bytes) + var numberString = originalString[2:] + log.Debugf("numberString: %v", numberString) + var number, errParsingInt = strconv.ParseInt(numberString, 16, 64) // nolint + if errParsingInt != nil { + return nil, errParsingInt + } + + return &token{TokenType: operationToken, Operation: createValueOperation(number, originalString)}, nil + } +} + func floatValue() lex.Action { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { var numberString = string(m.Bytes) @@ -328,6 +342,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`\|`), opToken(pipeOpType)) + lexer.Add([]byte(`0[xX][0-9A-Fa-f]+`), hexValue()) lexer.Add([]byte(`-?\d+(\.\d+)`), floatValue()) lexer.Add([]byte(`-?[1-9](\.\d+)?[Ee][-+]?\d+`), floatValue()) lexer.Add([]byte(`-?\d+`), numberValue()) diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 1c3fa15c..bdf1881c 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -6,6 +6,8 @@ import ( "bytes" "container/list" "fmt" + "strconv" + "strings" logging "gopkg.in/op/go-logging.v1" yaml "gopkg.in/yaml.v3" @@ -117,6 +119,17 @@ type Operation struct { UpdateAssign bool // used for assign ops, when true it means we evaluate the rhs given the lhs } +// yaml numbers can be hex encoded... +func parseInt(numberString string) (string, int64, error) { + if strings.HasPrefix(numberString, "0x") || + strings.HasPrefix(numberString, "0X") { + num, err := strconv.ParseInt(numberString[2:], 16, 64) // nolint + return "0x%X", num, err + } + num, err := strconv.ParseInt(numberString, 10, 64) // nolint + return "%v", num, err +} + func createScalarNode(value interface{}, stringValue string) *yaml.Node { var node = &yaml.Node{Kind: yaml.ScalarNode} node.Value = stringValue diff --git a/pkg/yqlib/operator_add.go b/pkg/yqlib/operator_add.go index b785bc9c..715fe1c4 100644 --- a/pkg/yqlib/operator_add.go +++ b/pkg/yqlib/operator_add.go @@ -76,17 +76,17 @@ func addScalars(target *CandidateNode, lhs *yaml.Node, rhs *yaml.Node) (*Candida target.Node.Tag = "!!str" target.Node.Value = lhs.Value + rhs.Value } else if lhs.Tag == "!!int" && rhs.Tag == "!!int" { - lhsNum, err := strconv.Atoi(lhs.Value) + format, lhsNum, err := parseInt(lhs.Value) if err != nil { return nil, err } - rhsNum, err := strconv.Atoi(rhs.Value) + _, rhsNum, err := parseInt(rhs.Value) if err != nil { return nil, err } sum := lhsNum + rhsNum target.Node.Tag = "!!int" - target.Node.Value = fmt.Sprintf("%v", sum) + target.Node.Value = fmt.Sprintf(format, sum) } else if (lhs.Tag == "!!int" || lhs.Tag == "!!float") && (rhs.Tag == "!!int" || rhs.Tag == "!!float") { lhsNum, err := strconv.ParseFloat(lhs.Value, 64) if err != nil { diff --git a/pkg/yqlib/operator_multiply.go b/pkg/yqlib/operator_multiply.go index d7af255e..ecf12623 100644 --- a/pkg/yqlib/operator_multiply.go +++ b/pkg/yqlib/operator_multiply.go @@ -91,15 +91,15 @@ func multiplyIntegers(lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, e target.Node.Style = lhs.Node.Style target.Node.Tag = "!!int" - lhsNum, err := strconv.Atoi(lhs.Node.Value) + format, lhsNum, err := parseInt(lhs.Node.Value) if err != nil { return nil, err } - rhsNum, err := strconv.Atoi(rhs.Node.Value) + _, rhsNum, err := parseInt(rhs.Node.Value) if err != nil { return nil, err } - target.Node.Value = fmt.Sprintf("%v", lhsNum*rhsNum) + target.Node.Value = fmt.Sprintf(format, lhsNum*rhsNum) return target, nil } diff --git a/pkg/yqlib/operator_subtract.go b/pkg/yqlib/operator_subtract.go index 9c5bcc0f..6a8b6742 100644 --- a/pkg/yqlib/operator_subtract.go +++ b/pkg/yqlib/operator_subtract.go @@ -62,17 +62,17 @@ func subtractScalars(target *CandidateNode, lhs *yaml.Node, rhs *yaml.Node) (*Ca if lhs.Tag == "!!str" { return nil, fmt.Errorf("strings cannot be subtracted") } else if lhs.Tag == "!!int" && rhs.Tag == "!!int" { - lhsNum, err := strconv.Atoi(lhs.Value) + format, lhsNum, err := parseInt(lhs.Value) if err != nil { return nil, err } - rhsNum, err := strconv.Atoi(rhs.Value) + _, rhsNum, err := parseInt(rhs.Value) if err != nil { return nil, err } result := lhsNum - rhsNum target.Node.Tag = "!!int" - target.Node.Value = fmt.Sprintf("%v", result) + target.Node.Value = fmt.Sprintf(format, result) } else if (lhs.Tag == "!!int" || lhs.Tag == "!!float") && (rhs.Tag == "!!int" || rhs.Tag == "!!float") { lhsNum, err := strconv.ParseFloat(lhs.Value, 64) if err != nil { diff --git a/pkg/yqlib/operator_value_test.go b/pkg/yqlib/operator_value_test.go index cc0aa36e..4821c1e3 100644 --- a/pkg/yqlib/operator_value_test.go +++ b/pkg/yqlib/operator_value_test.go @@ -12,6 +12,76 @@ var valueOperatorScenarios = []expressionScenario{ "D0, P[], (!!int)::1\n", }, }, + { + document: ``, + expression: `0x9f`, + expected: []string{ + "D0, P[], (!!int)::0x9f\n", + }, + }, + { + document: ``, + expression: `0x1A`, + expected: []string{ + "D0, P[], (!!int)::0x1A\n", + }, + }, + { + document: ``, + expression: `0x1A + 2`, + expected: []string{ + "D0, P[], (!!int)::0x1C\n", + }, + }, + { + document: ``, + expression: `0x12 * 2`, + expected: []string{ + "D0, P[], (!!int)::0x24\n", + }, + }, + { + document: ``, + expression: `0xF - 1`, + expected: []string{ + "D0, P[], (!!int)::0xE\n", + }, + }, + { + document: ``, + expression: `12`, + expected: []string{ + "D0, P[], (!!int)::12\n", + }, + }, + { + document: ``, + expression: `12 + 2`, + expected: []string{ + "D0, P[], (!!int)::14\n", + }, + }, + { + document: ``, + expression: `12 * 2`, + expected: []string{ + "D0, P[], (!!int)::24\n", + }, + }, + { + document: ``, + expression: `12 - 2`, + expected: []string{ + "D0, P[], (!!int)::10\n", + }, + }, + { + document: ``, + expression: `0X12`, + expected: []string{ + "D0, P[], (!!int)::0X12\n", + }, + }, { document: ``, expression: `-1`,