Merge pull request #172 from travisturner/float-to-decimal

use pql.Decimal instead of float64
This commit is contained in:
Travis Turner 2020-03-16 08:35:19 -05:00 committed by GitHub
commit 22ae1139d1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 2319 additions and 1418 deletions

View file

@ -2926,7 +2926,7 @@ func (e *executor) executeSet(ctx context.Context, index string, c *pql.Call, op
// Read row value.
rowVal, err := getScaledInt(f, v)
if err != nil {
return false, fmt.Errorf("reading Set() row: %v", err)
return false, fmt.Errorf("reading Set() row (int/decimal): %v", err)
}
return e.executeSetValueField(ctx, index, c, f, colID, rowVal, opt)
@ -4477,6 +4477,8 @@ func getScaledInt(f *Field, v interface{}) (int64, error) {
value = int64(float64(tv) * math.Pow10(int(scale)))
case uint64:
value = int64(float64(tv) * math.Pow10(int(scale)))
case pql.Decimal:
value = tv.ToInt64(scale)
case float64:
value = int64(tv * math.Pow10(int(scale)))
default:

View file

@ -34,6 +34,7 @@ import (
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/boltdb"
"github.com/pilosa/pilosa/v2/http"
"github.com/pilosa/pilosa/v2/pql"
"github.com/pilosa/pilosa/v2/server"
"github.com/pilosa/pilosa/v2/test"
"github.com/pkg/errors"
@ -1463,6 +1464,63 @@ func TestExecutor_Execute_MinMax(t *testing.T) {
})
}
})
t.Run("Decimal", func(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
hldr := test.Holder{Holder: c[0].Server.Holder()}
idx, err := hldr.CreateIndex("i", pilosa.IndexOptions{})
if err != nil {
t.Fatal(err)
}
tests := []struct {
scale int64
min int64
max int64
set pql.Decimal
}{
{2, 10, 20, pql.Decimal{Value: 115, Scale: 1}},
{2, -10, 20, pql.Decimal{Value: 115, Scale: 1}},
{2, -10, 20, pql.Decimal{Value: -95, Scale: 1}},
{2, -20, -10, pql.Decimal{Value: -115, Scale: 1}},
}
for i, test := range tests {
fld := fmt.Sprintf("f%d", i)
t.Run("MinMaxField_"+fld, func(t *testing.T) {
if _, err := idx.CreateField(fld, pilosa.OptFieldTypeDecimal(test.scale, test.min, test.max)); err != nil {
t.Fatal(err)
}
if _, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: fmt.Sprintf(`
Set(10, %s=%s)
`, fld, test.set)}); err != nil {
t.Fatal(err)
}
var pql string
t.Run("Min", func(t *testing.T) {
pql = fmt.Sprintf(`Min(field=%s)`, fld)
if result, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: pql}); err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(result.Results[0], pilosa.ValCount{FloatVal: test.set.Float64(), Count: 1}) {
t.Fatalf("unexpected min result, test %d: %s", i, spew.Sdump(result))
}
})
t.Run("Max", func(t *testing.T) {
pql = fmt.Sprintf(`Max(field=%s)`, fld)
if result, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: pql}); err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(result.Results[0], pilosa.ValCount{FloatVal: test.set.Float64(), Count: 1}) {
t.Fatalf("unexpected max result, test %d: %s", i, spew.Sdump(result))
}
})
})
}
})
})
t.Run("ColumnID", func(t *testing.T) {

View file

@ -194,6 +194,10 @@ func OptFieldTypeDecimal(scale int64, minmax ...int64) FieldOption {
fo.Max = math.MaxInt64
if len(minmax) == 2 {
min, max := minmax[0], minmax[1]
if scale != 0 {
min = int64(float64(min) * math.Pow10(int(scale)))
max = int64(float64(max) * math.Pow10(int(scale)))
}
if min > max {
return errors.Errorf("decimal field min cannot be greater than max, got %d, %d", min, max)
}
@ -202,7 +206,13 @@ func OptFieldTypeDecimal(scale int64, minmax ...int64) FieldOption {
} else if len(minmax) > 2 {
return errors.Errorf("unknown extra parameters beyond min and max: %v", minmax)
} else if len(minmax) == 1 {
fo.Min = minmax[0]
// It's not necessary to handle the scale==0 case separately,
// but it avoids the type conversion.
if scale == 0 {
fo.Min = minmax[0]
} else {
fo.Min = int64(float64(minmax[0]) * math.Pow10(int(scale)))
}
}
fo.Type = FieldTypeDecimal
fo.Base = bsiBase(fo.Min, fo.Max)
@ -1447,7 +1457,7 @@ func (f *Field) MaxForShard(shard uint64, filter *Row) (ValCount, error) {
valCount := ValCount{Count: int64(cnt)}
if f.Options().Type == FieldTypeDecimal {
valCount.FloatVal = float64(max) / math.Pow10(int(bsig.Scale))
valCount.FloatVal = float64(max+bsig.Base) / math.Pow10(int(bsig.Scale))
} else {
valCount.Val = max + bsig.Base
}
@ -1482,7 +1492,7 @@ func (f *Field) MinForShard(shard uint64, filter *Row) (ValCount, error) {
valCount := ValCount{Count: int64(cnt)}
if f.Options().Type == FieldTypeDecimal {
valCount.FloatVal = float64(min) / math.Pow10(int(bsig.Scale))
valCount.FloatVal = float64(min+bsig.Base) / math.Pow10(int(bsig.Scale))
} else {
valCount.Val = min + bsig.Base
}

View file

@ -62,7 +62,7 @@ func (q *Query) lastCallStackElem() *callStackElem {
func (q *Query) addPosNum(key, value string) {
q.addField(key)
q.addNumVal(value)
q.addNumVal(value, false)
}
func (q *Query) addPosStr(key, value string) {
@ -87,9 +87,9 @@ func (q *Query) endConditional() {
if len(q.conditional) != 5 {
panic(fmt.Sprintf("conditional of wrong length: %#v", q.conditional))
}
low := parseNum(q.conditional[0])
low := parseNum(q.conditional[0], false)
field := q.conditional[2]
high := parseNum(q.conditional[4])
high := parseNum(q.conditional[4], false)
var op Token
switch q.conditional[1] + q.conditional[3] {
@ -157,12 +157,12 @@ func (q *Query) addVal(val interface{}) {
elem.lastCond = ILLEGAL
}
func (q *Query) addNumVal(val string) {
func (q *Query) addNumVal(val string, asFloat bool) {
elem := q.lastCallStackElem()
if elem == nil || elem.lastField == "" {
panic(fmt.Sprintf("addIntVal called with '%s' when lastField is empty", val))
}
ival := parseNum(val)
ival := parseNum(val, asFloat)
if elem.inList {
if elem.lastCond != ILLEGAL {
list := elem.call.Args[elem.lastField].(*Condition).Value.([]interface{})
@ -968,11 +968,15 @@ func joinUint64Slice(a []uint64) string {
return "[" + strings.Join(other, ",") + "]"
}
func parseNum(val string) interface{} {
func parseNum(val string, asFloat bool) interface{} {
var ival interface{}
var err error
if strings.Contains(val, ".") {
ival, err = strconv.ParseFloat(val, 64)
if asFloat {
ival, err = strconv.ParseFloat(val, 64)
} else {
ival, err = ParseDecimal(val)
}
} else {
ival, err = strconv.ParseInt(val, 10, 64)
}

240
pql/decimal.go Normal file
View file

@ -0,0 +1,240 @@
// Copyright 2017 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pql
import (
"fmt"
"math"
"strconv"
"strings"
"github.com/pkg/errors"
)
// Decimal represents a decimal value; the intention
// is to avoid relying on float64, and the primary
// purpose is to have a predictable way to encode such
// values used in query strings.
// Scale is the number of digits to the right of the
// decimal point.
// Precision is currently not considered; precision, for
// our purposes is implied to be the complete, known value.
type Decimal struct {
Value int64
Scale int64
}
// ToInt64 returns d as an int64 adjusted to the
// provided scale.
func (d Decimal) ToInt64(scale int64) int64 {
var ret int64
scaleDiff := scale - d.Scale
if scaleDiff == 0 {
ret = d.Value
} else {
ret = int64(float64(d.Value) * math.Pow10(int(scaleDiff)))
}
return ret
}
// Float64 returns d as a float64.
func (d Decimal) Float64() float64 {
var ret float64
if d.Scale == 0 {
ret = float64(d.Value)
} else {
ret = float64(d.Value) / math.Pow10(int(d.Scale))
}
return ret
}
// String returns the string representation of the decimal.
func (d Decimal) String() string {
var s string
var neg bool
sval := fmt.Sprintf("%d", d.Value)
// Strip the negative sign off for now, and
// re-apply it at the end.
if sval[0] == '-' {
neg = true
sval = sval[1:]
}
if d.Scale == 0 {
s = sval
} else if d.Scale < 0 {
s = sval + strings.Repeat("0", int(-1*d.Scale))
} else {
var bufLen int
if int(d.Scale) < len(sval) {
bufLen = len(sval) + 1
} else {
bufLen = int(d.Scale) + 2
}
buf := make([]byte, bufLen)
j := 0
for i := range buf {
z := len(buf) - 1 - i // index into buf from the end
if i == int(d.Scale) {
buf[z] = '.'
continue
}
if len(sval) > j {
buf[z] = sval[len(sval)-1-j]
j++
} else {
buf[z] = '0'
}
}
s = string(buf)
}
if neg {
return "-" + s
}
return s
}
const (
stateSign = "sign"
stateLeadingZeros = "zeros"
stateMantissa = "mantissa"
)
// ParseDecimal parses a string into a Decimal.
func ParseDecimal(s string) (Decimal, error) {
var sign bool
var value int64
var scale int64
var err error
// General steps:
// - Trim leading whitespace/zeros
// - Get the sign value
// - Trim leading zeros
// - Push characters into a buffer
// - Track position of decimal point
// - Trim trailing zeros of buffer
// - value = buffer -> int
// - scale = len(buffer) - tracked position
var decimalPos int = -1
var pos int
mantissa := make([]byte, len(s))
state := stateSign
var foundLeadingZero bool
for i := 0; i < len(s); i++ {
switch state {
case stateSign:
switch s[i] {
case ' ':
continue
case '-':
sign = true
fallthrough
case '+':
state = stateLeadingZeros
default:
state = stateLeadingZeros
i--
}
case stateLeadingZeros:
switch s[i] {
case '0':
foundLeadingZero = true
continue
default:
state = stateMantissa
i--
}
case stateMantissa:
switch s[i] {
case '.':
if decimalPos == -1 {
decimalPos = pos
} else {
return Decimal{}, errors.Errorf("invalid decimal string: %s", s)
}
continue
default:
mantissa[pos] = s[i]
pos++
}
}
}
// If we've gotten here and state is still in stateSign or
// it's in stateLeadingZeros without finding any zeros,
// it means no value was provided.
if state == stateSign || (state == stateLeadingZeros && !foundLeadingZero) {
return Decimal{}, errors.New("decimal string is empty")
}
// Trim trailing zeros/spaces of mantissa
// for any portion that would have been to the
// right of the decimal.
trimZeroCnt := 0
trimSpaceCnt := 0
for i := len(mantissa) - 1; i >= 0; i-- {
switch mantissa[i] {
case uint8(0), uint8(32): // nil/space
trimSpaceCnt++
continue
case uint8(48): // zero
trimZeroCnt++
continue
}
break
}
mantissa = mantissa[:len(mantissa)-trimSpaceCnt-trimZeroCnt]
// Based on where (or if) the decimal was found,
// calculate scale.
if decimalPos == -1 {
scale = -1 * int64(trimZeroCnt)
} else {
scale = int64(len(mantissa) - decimalPos)
}
// If mantissa is empty, treat it as "0".
if len(mantissa) == 0 {
mantissa = []byte{'0'}
sign = false
scale = 0
}
value, err = strconv.ParseInt(string(mantissa), 10, 64)
if err != nil {
return Decimal{}, errors.Wrap(err, "converting mantissa to uint32")
}
// Because we pulled the sign off at the beginning, if value is
// negative here, it likely means the string had two "-"" characters.
if value < 0 {
return Decimal{}, errors.New("invalid negative value")
}
if sign {
value *= -1
}
return Decimal{
Value: value,
Scale: scale,
}, nil
}

164
pql/decimal_test.go Normal file
View file

@ -0,0 +1,164 @@
// Copyright 2017 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pql_test
import (
"strings"
"testing"
"github.com/pilosa/pilosa/v2/pql"
)
// Ensure call can be converted into a string.
func TestDecimal(t *testing.T) {
t.Run("Parse", func(t *testing.T) {
tests := []struct {
s string
exp pql.Decimal
expErr string
}{
{"0", pql.Decimal{0, 0}, ""},
{"-0", pql.Decimal{0, 0}, ""},
{"0.0", pql.Decimal{0, 0}, ""},
{"-0.00", pql.Decimal{0, 0}, ""},
{"123.4567", pql.Decimal{1234567, 4}, ""},
{" 123.4567", pql.Decimal{1234567, 4}, ""},
{" 123.4567 ", pql.Decimal{1234567, 4}, ""},
{"123.456700", pql.Decimal{1234567, 4}, ""},
{"00123.4567", pql.Decimal{1234567, 4}, ""},
{"+123.4567", pql.Decimal{1234567, 4}, ""},
{"-123.4567", pql.Decimal{-1234567, 4}, ""},
{"-00123.4567", pql.Decimal{-1234567, 4}, ""},
{"-12.25", pql.Decimal{-1225, 2}, ""},
{"123", pql.Decimal{123, 0}, ""},
{"-12300", pql.Decimal{-123, -2}, ""},
{"+012300", pql.Decimal{123, -2}, ""},
{"12300", pql.Decimal{123, -2}, ""},
{"12300.", pql.Decimal{123, -2}, ""},
{"12300.0", pql.Decimal{123, -2}, ""},
{"123.0", pql.Decimal{123, 0}, ""},
{".123", pql.Decimal{123, 3}, ""},
{"0.123", pql.Decimal{123, 3}, ""},
{"0.001230", pql.Decimal{123, 5}, ""},
{" 0.001230 ", pql.Decimal{123, 5}, ""},
{"-0.001230 ", pql.Decimal{-123, 5}, ""},
// int64 edges.
{".000009223372036854775807", pql.Decimal{9223372036854775807, 24}, ""},
{"-.000009223372036854775807", pql.Decimal{-9223372036854775807, 24}, ""},
{"92233720368547.75807", pql.Decimal{9223372036854775807, 5}, ""},
{"-92233720368547.75807", pql.Decimal{-9223372036854775807, 5}, ""},
{"9223372036854775807000", pql.Decimal{9223372036854775807, -3}, ""},
{"-9223372036854775807000", pql.Decimal{-9223372036854775807, -3}, ""},
// Error cases.
{"", pql.Decimal{}, "decimal string is empty"},
{"-", pql.Decimal{}, "decimal string is empty"},
{"*0.123", pql.Decimal{}, "invalid syntax"},
{"abc", pql.Decimal{}, "invalid syntax"},
{"0.12.3", pql.Decimal{}, "invalid decimal string"},
{"--12300", pql.Decimal{}, "invalid negative value"},
{"922337203685477580.8", pql.Decimal{}, "value out of range"},
{"-922337203685477580.8", pql.Decimal{}, "value out of range"},
{"9223372036854775808000", pql.Decimal{}, "value out of range"},
{"-9223372036854775808000", pql.Decimal{}, "value out of range"},
}
for i, test := range tests {
dec, err := pql.ParseDecimal(test.s)
if test.expErr != "" {
if err == nil || !strings.Contains(err.Error(), test.expErr) {
t.Fatalf("test %d expected error to contain: %s, but got: %v", i, test.expErr, err)
}
} else if err != nil {
t.Fatalf("test %d parsing string `%s`: %s", i, test.s, err)
} else if dec != test.exp {
t.Fatalf("test %d expected: %v, but got: %v", i, test.exp, dec)
}
}
})
t.Run("ToInt64", func(t *testing.T) {
tests := []struct {
dec pql.Decimal
scale int64
exp int64
}{
{pql.Decimal{0, 0}, 0, 0}, // 0 : 0
{pql.Decimal{0, 0}, 1, 0}, // 0 : 0.0
{pql.Decimal{0, 0}, -1, 0}, // 0 : 0
{pql.Decimal{1234567, 4}, 5, 12345670}, // 123.4567 : 123.45670
{pql.Decimal{1234567, 4}, 4, 1234567}, // 123.4567 : 123.4567
{pql.Decimal{1234567, 4}, 3, 123456}, // 123.4567 : 123.456
{pql.Decimal{-1234567, 4}, 5, -12345670}, // -123.4567 : -123.45670
{pql.Decimal{-1234567, 4}, 4, -1234567}, // -123.4567 : -123.4567
{pql.Decimal{-1234567, 4}, 3, -123456}, // -123.4567 : -123.456
{pql.Decimal{123, -2}, 5, 1230000000}, // 12300 : 12300.00000
{pql.Decimal{123, -2}, -1, 1230}, // 12300 : 1230
{pql.Decimal{123, 1}, -1, 1}, // 12.3 : 1
{pql.Decimal{123, 1}, -2, 0}, // 12.3 : 0
}
for i, test := range tests {
v := test.dec.ToInt64(test.scale)
if v != test.exp {
t.Fatalf("test %d expected: %d, but got: %d", i, test.exp, v)
}
}
})
t.Run("String", func(t *testing.T) {
tests := []struct {
s string
exp string
}{
{"123.4567", "123.4567"},
{" 123.4567", "123.4567"},
{" 123.4567 ", "123.4567"},
{"123.456700", "123.4567"},
{"00123.4567", "123.4567"},
{"+123.4567", "123.4567"},
{"-123.4567", "-123.4567"},
{"-00123.4567", "-123.4567"},
{"-12.25", "-12.25"},
{"123", "123"},
{"-12300", "-12300"},
{"+012300", "12300"},
{"12300", "12300"},
{"12300.", "12300"},
{"12300.0", "12300"},
{"123.0", "123"},
{"0.123", "0.123"},
{"0.001230", "0.00123"},
{" 0.001230 ", "0.00123"},
{"-0.001230 ", "-0.00123"},
}
for i, test := range tests {
dec, err := pql.ParseDecimal(test.s)
if err != nil {
t.Fatalf("parsing string `%s`: %s", test.s, err)
}
if str := dec.String(); str != test.exp {
t.Fatalf("test %d expected: %s, but got: %s", i, test.exp, str)
}
}
})
}

View file

@ -96,8 +96,8 @@ func TestParser_Parse(t *testing.T) {
}
})
// Parse with float arguments.
t.Run("WithFloatArgs", func(t *testing.T) {
// Parse with decimal arguments.
t.Run("WithDecimalArgs", func(t *testing.T) {
q, err := pql.ParseString(`Row( key=12.25, foo= 13.167, bar=2., baz=0.9)`)
if err != nil {
t.Fatal(err)
@ -105,10 +105,10 @@ func TestParser_Parse(t *testing.T) {
&pql.Call{
Name: "Row",
Args: map[string]interface{}{
"key": 12.25,
"foo": 13.167,
"bar": 2.,
"baz": 0.9,
"key": pql.Decimal{1225, 2},
"foo": pql.Decimal{13167, 3},
"bar": pql.Decimal{2, 0},
"baz": pql.Decimal{9, 1},
},
},
) {
@ -125,7 +125,7 @@ func TestParser_Parse(t *testing.T) {
&pql.Call{
Name: "Row",
Args: map[string]interface{}{
"key": -12.25,
"key": pql.Decimal{-1225, 2},
"foo": int64(-13),
},
},
@ -181,7 +181,7 @@ func TestParser_Parse(t *testing.T) {
Name: "Row",
Args: map[string]interface{}{
"key": "foo",
"x": &pql.Condition{Op: pql.EQ, Value: 12.25},
"x": &pql.Condition{Op: pql.EQ, Value: pql.Decimal{1225, 2}},
"y": &pql.Condition{Op: pql.GTE, Value: int64(100)},
"z": &pql.Condition{Op: pql.BETWEEN, Value: []interface{}{int64(4), int64(8)}},
"m": &pql.Condition{Op: pql.NEQ, Value: nil},
@ -191,5 +191,4 @@ func TestParser_Parse(t *testing.T) {
t.Fatalf("unexpected call: %#v", q.Calls[0])
}
})
}

View file

@ -6,22 +6,27 @@ type PQL Peg {
Calls <- sp (Call sp)* !.
Call <- 'Set' {p.startCall("Set")} open col comma args (comma timestamp)? close {p.endCall()}
/ 'SetRowAttrs' {p.startCall("SetRowAttrs")} open posfield comma row comma args close {p.endCall()}
/ 'SetColumnAttrs' {p.startCall("SetColumnAttrs")} open col comma args close {p.endCall()}
/ 'Clear' {p.startCall("Clear")} open col comma args close {p.endCall()}
/ 'ClearRow' {p.startCall("ClearRow")} open arg close {p.endCall()}
/ 'Store' {p.startCall("Store")} open Call comma arg close {p.endCall()}
Call <- 'Set' {p.startCall("Set")} open col comma dargs (comma timestamp)? close {p.endCall()}
/ 'SetRowAttrs' {p.startCall("SetRowAttrs")} open posfield comma row comma fargs close {p.endCall()}
/ 'SetColumnAttrs' {p.startCall("SetColumnAttrs")} open col comma fargs close {p.endCall()}
/ 'Clear' {p.startCall("Clear")} open col comma dargs close {p.endCall()}
/ 'ClearRow' {p.startCall("ClearRow")} open darg close {p.endCall()}
/ 'Store' {p.startCall("Store")} open Call comma darg close {p.endCall()}
/ 'TopN' {p.startCall("TopN")} open posfield (comma allargs)? close {p.endCall()}
/ 'Rows' {p.startCall("Rows")} open posfield (comma allargs)? close {p.endCall()}
/ 'Range' {p.startCall("Range")} open field sp '=' sp value comma 'from='? {p.addField("from")} timestampfmt {p.addVal(buffer[begin:end])} comma 'to='? sp {p.addField("to")} timestampfmt {p.addVal(buffer[begin:end])} close {p.endCall()}
/ 'Range' {p.startCall("Range")} open field sp '=' sp fvalue comma 'from='? {p.addField("from")} timestampfmt {p.addVal(buffer[begin:end])} comma 'to='? sp {p.addField("to")} timestampfmt {p.addVal(buffer[begin:end])} close {p.endCall()}
/ < IDENT > { p.startCall(buffer[begin:end] ) } open allargs comma? close { p.endCall() }
allargs <- Call (comma Call)* (comma args)? / args / sp
args <- arg (comma args)? sp
arg <- ( field sp '=' sp value
/ field sp COND sp value
/ conditional
)
allargs <- Call (comma Call)* (comma dargs)? / dargs / sp
fargs <- farg (comma fargs)? sp
farg <- ( field sp '=' sp fvalue
/ field sp COND sp fvalue
/ conditional
)
dargs <- darg (comma dargs)? sp
darg <- ( field sp '=' sp dvalue
/ field sp COND sp dvalue
/ conditional
)
COND <- ( '><' { p.addBTWN() }
/ '<=' { p.addLTE() }
/ '>=' { p.addGTE() }
@ -36,21 +41,38 @@ condint <- < '-'? [0-9]* '.' [0-9]+ / '0' / '-'? [1-9] [0-9]* > sp {p.condAdd(bu
condLT <- <('<=' / '<')> sp {p.condAdd(buffer[begin:end])}
condfield <- <fieldExpr> sp {p.condAdd(buffer[begin:end])}
value <- ( item
/ lbrack { p.startList() } list rbrack { p.endList() }
dvalue <- ( ditem
/ lbrack { p.startList() } dlist rbrack { p.endList() }
)
list <- item (comma list)?
item <- ( 'null' &(comma / sp close) { p.addVal(nil) }
fvalue <- ( fitem
/ lbrack { p.startList() } flist rbrack { p.endList() }
)
dlist <- ditem (comma dlist)?
flist <- fitem (comma flist)?
ditem <- ( itema
/ decimal
/ itemb
)
fitem <- ( itema
/ float
/ itemb
)
itema <- ( 'null' &(comma / sp close) { p.addVal(nil) }
/ 'true' &(comma / sp close) { p.addVal(true) }
/ 'false' &(comma / sp close) { p.addVal(false) }
/ timestampfmt { p.addVal(buffer[begin:end]) }
/ < '-'? [0-9]+ ('.'[0-9]*)? > { p.addNumVal(buffer[begin:end]) }
/ < '-'? '.'[0-9]+ > { p.addNumVal(buffer[begin:end]) }
/ < IDENT > { p.startCall(buffer[begin:end]) } open allargs comma? close { p.addVal(p.endCall()) }
)
itemb <- ( < IDENT > { p.startCall(buffer[begin:end]) } open allargs comma? close { p.addVal(p.endCall()) }
/ < ([[A-Z]] / [0-9] / '-' / '_' / ':')+ > { p.addVal(buffer[begin:end]) }
/ < '"' doublequotedstring '"' > { s, _ := strconv.Unquote(buffer[begin:end]); p.addVal(s) }
/ '\'' < singlequotedstring > '\'' { p.addVal(buffer[begin:end]) }
)
float <- ( < '-'? [0-9]+ ('.'[0-9]*)? > { p.addNumVal(buffer[begin:end], true) }
/ < '-'? '.'[0-9]+ > { p.addNumVal(buffer[begin:end], true) }
)
decimal <- ( < '-'? [0-9]+ ('.'[0-9]*)? > { p.addNumVal(buffer[begin:end], false) }
/ < '-'? '.'[0-9]+ > { p.addNumVal(buffer[begin:end], false) }
)
doublequotedstring <- ( '\\"' / '\\\\' / [^"] )*
singlequotedstring <- ( '\\\'' / '\\\\' / [^'] )*

File diff suppressed because it is too large Load diff