Skip to content

Commit

Permalink
Fix error reporting for invalid numbers + correctly set Pos when usin…
Browse files Browse the repository at this point in the history
…g custom Capture interfaces.

Fixes #34. Fixes #33.
  • Loading branch information
alecthomas committed Oct 3, 2018
1 parent 6709c56 commit f14a17d
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 66 deletions.
2 changes: 1 addition & 1 deletion _examples/protobuf/main.go
@@ -1,4 +1,4 @@
// nolint: govet
// nolint: govet, golint
package main

import (
Expand Down
12 changes: 6 additions & 6 deletions lookahead_test.go
Expand Up @@ -89,12 +89,12 @@ type LAT3Grammar struct {

type LAT3Expense struct {
Name string `@Ident "paid"`
Amount *LAT3Value `@@`
Amount *LAT3Value `@@ { Ident } "."`
}

type LAT3Value struct {
Float float64 ` "$" @Float {@Ident} "."`
Integer int `| "$" @Int {@Ident} "."`
Float float64 ` "$" @Float`
Integer int `| "$" @Int`
}

func TestIssue11(t *testing.T) {
Expand All @@ -110,9 +110,9 @@ func TestIssue11(t *testing.T) {
g,
&LAT3Grammar{
Expenses: []*LAT3Expense{
{Name: "A", Amount: &LAT3Value{Float: 32.8}},
{Name: "B", Amount: &LAT3Value{Integer: 72}},
{Name: "C", Amount: &LAT3Value{Float: 65.5}},
{Name: "A", Amount: &LAT3Value{Float: 30.8}},
{Name: "B", Amount: &LAT3Value{Integer: 70}},
{Name: "C", Amount: &LAT3Value{Float: 63.5}},
},
},
)
Expand Down
57 changes: 43 additions & 14 deletions nodes.go
Expand Up @@ -6,6 +6,7 @@ import (
"reflect"
"strconv"
"strings"
"unsafe"

"github.com/alecthomas/participle/lexer"
)
Expand Down Expand Up @@ -291,37 +292,59 @@ func conform(t reflect.Type, values []reflect.Value) (out []reflect.Value) {
v = v.Addr()
}

switch t.Kind() {
kind := t.Kind()
switch kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
n, err := strconv.ParseInt(v.String(), 0, 64)
if err == nil {
v = reflect.New(t).Elem()
v.SetInt(n)
n, err := strconv.ParseInt(v.String(), 0, sizeOfKind(kind))
if err != nil {
panicf("invalid integer %q: %s", v.String(), err)
}
v = reflect.New(t).Elem()
v.SetInt(n)

case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
n, err := strconv.ParseUint(v.String(), 0, 64)
if err == nil {
v = reflect.New(t).Elem()
v.SetUint(n)
n, err := strconv.ParseUint(v.String(), 0, sizeOfKind(kind))
if err != nil {
panicf("invalid integer %q: %s", v.String(), err)
}
v = reflect.New(t).Elem()
v.SetUint(n)

case reflect.Bool:
v = reflect.ValueOf(true)

case reflect.Float32, reflect.Float64:
n, err := strconv.ParseFloat(v.String(), 64)
if err == nil {
v = reflect.New(t).Elem()
v.SetFloat(n)
n, err := strconv.ParseFloat(v.String(), sizeOfKind(kind))
if err != nil {
panicf("invalid integer %q: %s", v.String(), err)
}
v = reflect.New(t).Elem()
v.SetFloat(n)
}

out = append(out, v)
}
return out
}

const sizeOfInt = int(unsafe.Sizeof(int(0)))

func sizeOfKind(kind reflect.Kind) int {
switch kind {
case reflect.Int8, reflect.Uint8:
return 8
case reflect.Int16, reflect.Uint16:
return 16
case reflect.Int32, reflect.Uint32, reflect.Float32:
return 32
case reflect.Int64, reflect.Uint64, reflect.Float64:
return 64
case reflect.Int, reflect.Uint:
return sizeOfInt
}
panic("unsupported kind " + kind.String())
}

// Set field.
//
// If field is a pointer the pointer will be set to the value. If field is a string, value will be
Expand Down Expand Up @@ -349,6 +372,12 @@ func setField(pos lexer.Position, strct reflect.Value, field structLexerField, f
}
}

if f.Kind() == reflect.Struct {
if pf := f.FieldByName("Pos"); pf.IsValid() && pf.Type() == positionType {
pf.Set(reflect.ValueOf(pos))
}
}

if f.CanAddr() {
if d, ok := f.Addr().Interface().(Capture); ok {
ifv := []string{}
Expand All @@ -372,7 +401,7 @@ func setField(pos lexer.Position, strct reflect.Value, field structLexerField, f
return
}

// Coalesce multiple tokens into one. This allow eg. ["-", "10"] to be captured as separate tokens but
// Coalesce multiple tokens into one. This allows eg. ["-", "10"] to be captured as separate tokens but
// parsed as a single string "-10".
if len(fieldValue) > 1 {
out := []string{}
Expand Down
119 changes: 74 additions & 45 deletions parser_test.go
@@ -1,6 +1,8 @@
package participle

import (
"fmt"
"math"
"strings"
"testing"

Expand Down Expand Up @@ -680,51 +682,6 @@ func TestParseable(t *testing.T) {
require.Equal(t, expected, actual)
}

func TestIncrementInt(t *testing.T) {
type grammar struct {
Field int `@"." { @"." }`
}

parser, err := Build(&grammar{})
require.NoError(t, err)

actual := &grammar{}
expected := &grammar{4}
err = parser.ParseString(`. . . .`, actual)
require.NoError(t, err)
require.Equal(t, expected, actual)
}

func TestIncrementUint(t *testing.T) {
type grammar struct {
Field uint `@"." { @"." }`
}

parser, err := Build(&grammar{})
require.NoError(t, err)

actual := &grammar{}
expected := &grammar{4}
err = parser.ParseString(`. . . .`, actual)
require.NoError(t, err)
require.Equal(t, expected, actual)
}

func TestIncrementFloat(t *testing.T) {
type grammar struct {
Field float32 `@"." { @"." }`
}

parser, err := Build(&grammar{})
require.NoError(t, err)

actual := &grammar{}
expected := &grammar{4}
err = parser.ParseString(`. . . .`, actual)
require.NoError(t, err)
require.Equal(t, expected, actual)
}

func TestStringConcat(t *testing.T) {
type grammar struct {
Field string `@"." { @"." }`
Expand Down Expand Up @@ -825,3 +782,75 @@ func TestNestedOptional(t *testing.T) {
err = p.ParseString(`(1)`, actual)
require.Error(t, err)
}

type captureableWithPosition struct {
Pos lexer.Position
Value string
}

func (c *captureableWithPosition) Capture(values []string) error {
c.Value = strings.Join(values, " ")
return nil
}

func TestIssue35(t *testing.T) {
type grammar struct {
Value *captureableWithPosition `@Ident`
}
p := mustTestParser(t, &grammar{})
actual := &grammar{}
err := p.ParseString(`hello`, actual)
require.NoError(t, err)
expected := &grammar{Value: &captureableWithPosition{
Pos: lexer.Position{Column: 1, Offset: 0, Line: 1},
Value: "hello",
}}
require.Equal(t, expected, actual)
}

func TestInvalidNumbers(t *testing.T) {
type grammar struct {
Int8 int8 ` "int8" @Int`
Int16 int16 `| "int16" @Int`
Int32 int32 `| "int32" @Int`
Int64 int64 `| "int64" @Int`
Uint8 uint8 `| "uint8" @Int`
Uint16 uint16 `| "uint16" @Int`
Uint32 uint32 `| "uint32" @Int`
Uint64 uint64 `| "uint64" @Int`
Float32 float32 `| "float32" @Float`
Float64 float64 `| "float64" @Float`
}

p := mustTestParser(t, &grammar{})

tests := []struct {
name string
input string
expected *grammar
err bool
}{
{name: "ValidInt8", input: "int8 127", expected: &grammar{Int8: 127}},
{name: "InvalidInt8", input: "int8 129", err: true},
{name: "ValidInt16", input: "int16 32767", expected: &grammar{Int16: 32767}},
{name: "InvalidInt16", input: "int16 32768", err: true},
{name: "ValidInt32", input: fmt.Sprintf("int32 %d", math.MaxInt32), expected: &grammar{Int32: math.MaxInt32}},
{name: "InvalidInt32", input: fmt.Sprintf("int32 %d", math.MaxInt32+1), err: true},
{name: "ValidInt64", input: fmt.Sprintf("int64 %d", math.MaxInt64), expected: &grammar{Int64: math.MaxInt64}},
{name: "InvalidInt64", input: "int64 9223372036854775808", err: true},
{name: "ValidFloat64", input: "float64 1234.5", expected: &grammar{Float64: 1234.5}},
{name: "InvalidFloat64", input: "float64 asdf", err: true},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual := &grammar{}
err := p.ParseString(test.input, actual)
if test.err {
require.Error(t, err, fmt.Sprintf("%#v", actual))
} else {
require.NoError(t, err)
require.Equal(t, test.expected, actual)
}
})
}
}

0 comments on commit f14a17d

Please sign in to comment.