Skip to content

Commit

Permalink
Allow parsing of non-root productions (#239)
Browse files Browse the repository at this point in the history
  • Loading branch information
mccolljr authored Jun 27, 2022
1 parent 71ce420 commit fe65687
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 39 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ jobs:
uses: actions/checkout@v2
- name: Init Hermit
run: ./bin/hermit env -r >> $GITHUB_ENV
- name: Test
- name: Test Participle
run: go test ./...
- name: Test Examples
run: cd ./_examples && go test ./...
lint:
name: Lint
runs-on: ubuntu-latest
Expand Down
9 changes: 3 additions & 6 deletions _examples/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,11 @@ go 1.18
require (
github.com/alecthomas/assert/v2 v2.0.3
github.com/alecthomas/go-thrift v0.0.0-20170109061633-7914173639b2
github.com/alecthomas/kong v0.5.0
github.com/alecthomas/participle/v2 v2.0.0-alpha1
github.com/alecthomas/kong v0.6.1
github.com/alecthomas/participle/v2 v2.0.0-alpha11
github.com/alecthomas/repr v0.1.0
)

require (
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/pkg/errors v0.9.1 // indirect
)
require github.com/hexops/gotextdiff v1.0.3 // indirect

replace github.com/alecthomas/participle/v2 => ../
15 changes: 6 additions & 9 deletions _examples/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ github.com/alecthomas/assert/v2 v2.0.3 h1:WKqJODfOiQG0nEJKFKzDIG3E29CN2/4zR9XGJz
github.com/alecthomas/assert/v2 v2.0.3/go.mod h1:b/+1DI2Q6NckYi+3mXyH3wFb8qG37K/DuK80n7WefXA=
github.com/alecthomas/go-thrift v0.0.0-20170109061633-7914173639b2 h1:gKv6LPDhF/G3cNribA+kZtNPiPpKabZGLhcJuEtp3ig=
github.com/alecthomas/go-thrift v0.0.0-20170109061633-7914173639b2/go.mod h1:CxCgO+NdpMdi9SsTlGbc0W+/UNxO3I0AabOEJZ3w61w=
github.com/alecthomas/kong v0.5.0 h1:u8Kdw+eeml93qtMZ04iei0CFYve/WPcA5IFh+9wSskE=
github.com/alecthomas/kong v0.5.0/go.mod h1:uzxf/HUh0tj43x1AyJROl3JT7SgsZ5m+icOv1csRhc0=
github.com/alecthomas/kong v0.6.1 h1:1kNhcFepkR+HmasQpbiKDLylIL8yh5B5y1zPp5bJimA=
github.com/alecthomas/kong v0.6.1/go.mod h1:JfHWDzLmbh/puW6I3V7uWenoh56YNVONW+w8eKeUr9I=
github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
github.com/alecthomas/repr v0.1.0/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
Expand All @@ -12,14 +12,11 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
4 changes: 2 additions & 2 deletions _examples/thrift/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,9 @@ func main() {
for _, file := range cli.Files {
thrift := &Thrift{}
r, err := os.Open(file)
ctx.FatalIfError(err, "")
ctx.FatalIfErrorf(err, "")
err = parser.Parse("", r, thrift)
ctx.FatalIfError(err, "")
ctx.FatalIfErrorf(err, "")
repr.Println(thrift)
}
}
2 changes: 1 addition & 1 deletion ebnf.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
//
// Productions are always upper cased. Lexer tokens are always lower case.
func (p *Parser) String() string {
return ebnf(p.root)
return ebnf(p.typeNodes[p.rootType])
}

type ebnfp struct {
Expand Down
55 changes: 35 additions & 20 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ type customDef struct {

// A Parser for a particular grammar and lexer.
type Parser struct {
root node
lex lexer.Definition
typ reflect.Type
rootType reflect.Type
typeNodes map[reflect.Type]node
useLookahead int
caseInsensitive map[string]bool
mappers []mapperByToken
Expand Down Expand Up @@ -105,14 +105,16 @@ func Build(grammar interface{}, options ...Option) (parser *Parser, err error) {
if v.Kind() == reflect.Interface {
v = v.Elem()
}
p.typ = v.Type()
p.root, err = context.parseType(p.typ)
p.rootType = v.Type()
rootNode, err := context.parseType(p.rootType)
if err != nil {
return nil, err
}
if err := validate(p.root); err != nil {
if err := validate(rootNode); err != nil {
return nil, err
}
p.typeNodes = context.typeNodes
p.typeNodes[p.rootType] = rootNode
return p, nil
}

Expand All @@ -138,15 +140,9 @@ func (p *Parser) Lex(filename string, r io.Reader) ([]lexer.Token, error) {
// This may return a Error.
func (p *Parser) ParseFromLexer(lex *lexer.PeekingLexer, v interface{}, options ...ParseOption) error {
rv := reflect.ValueOf(v)
if rv.Kind() == reflect.Interface {
rv = rv.Elem()
}
rt := rv.Type()
if rt != p.typ {
return fmt.Errorf("must parse into value of type %s not %T", p.typ, v)
}
if rt.Kind() != reflect.Ptr || rt.Elem().Kind() != reflect.Struct {
return fmt.Errorf("target must be a pointer to a struct, not %s", rt)
parseNode, err := p.parseNodeFor(rv)
if err != nil {
return err
}
caseInsensitive := map[lexer.TokenType]bool{}
for sym, tt := range p.lex.Symbols() {
Expand All @@ -163,7 +159,7 @@ func (p *Parser) ParseFromLexer(lex *lexer.PeekingLexer, v interface{}, options
if parseable, ok := v.(Parseable); ok {
return p.rootParseable(ctx, parseable)
}
return p.parseOne(ctx, rv)
return p.parseOne(ctx, parseNode, rv)
}

func (p *Parser) parse(lex lexer.Lexer, v interface{}, options ...ParseOption) (err error) {
Expand Down Expand Up @@ -223,8 +219,8 @@ func (p *Parser) ParseBytes(filename string, b []byte, v interface{}, options ..
return p.parse(lex, v, options...)
}

func (p *Parser) parseOne(ctx *parseContext, rv reflect.Value) error {
err := p.parseInto(ctx, rv)
func (p *Parser) parseOne(ctx *parseContext, parseNode node, rv reflect.Value) error {
err := p.parseInto(ctx, parseNode, rv)
if err != nil {
return err
}
Expand All @@ -235,11 +231,11 @@ func (p *Parser) parseOne(ctx *parseContext, rv reflect.Value) error {
return nil
}

func (p *Parser) parseInto(ctx *parseContext, rv reflect.Value) error {
func (p *Parser) parseInto(ctx *parseContext, parseNode node, rv reflect.Value) error {
if rv.IsNil() {
return fmt.Errorf("target must be a non-nil pointer to a struct, but is a nil %s", rv.Type())
return fmt.Errorf("target must be a non-nil pointer to a struct or interface, but is a nil %s", rv.Type())
}
pv, err := p.root.Parse(ctx, rv.Elem())
pv, err := p.typeNodes[rv.Type().Elem()].Parse(ctx, rv.Elem())
if len(pv) > 0 && pv[0].Type() == rv.Elem().Type() {
rv.Elem().Set(reflect.Indirect(pv[0]))
}
Expand Down Expand Up @@ -281,3 +277,22 @@ func (p *Parser) getElidedTypes() []lexer.TokenType {
}
return elideTypes
}

func (p *Parser) parseNodeFor(v reflect.Value) (node, error) {
t := v.Type()
if t.Kind() == reflect.Interface {
t = t.Elem()
}
if t.Kind() != reflect.Ptr || (t.Elem().Kind() != reflect.Struct && t.Elem().Kind() != reflect.Interface) {
return nil, fmt.Errorf("expected a pointer to a struct or interface, but got %s", t)
}
parseNode := p.typeNodes[t]
if parseNode == nil {
t = t.Elem()
parseNode = p.typeNodes[t]
}
if parseNode == nil {
return nil, fmt.Errorf("parser does not know how to parse values of type %s", t)
}
return parseNode, nil
}
42 changes: 42 additions & 0 deletions parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1850,3 +1850,45 @@ BMember1 = <int> | <float> .
BMember2 = "{" TestUnionA "}" .
`), parser.String())
}

func TestParseSubProduction(t *testing.T) {
type (
ListItem struct {
Number *float64 `(@Int | @Float)`
String *string `| @String`
}

Grammar struct {
List []ListItem `"[" @@ ("," @@)* "]"`
}
)

numberItem := func(n float64) ListItem { return ListItem{Number: &n} }
stringItem := func(s string) ListItem { return ListItem{String: &s} }

p := mustTestParser(t, &Grammar{}, participle.Unquote())

var (
actual Grammar
expected = Grammar{List: []ListItem{numberItem(1), stringItem("test")}}
)

require.NoError(t, p.ParseString("", `[ 1, "test" ]`, &actual))
require.Equal(t, expected, actual)

var (
actualItem ListItem
expectedItem = numberItem(1.234e5)
)

require.NoError(t, p.ParseString("", `1.234e5`, &actualItem))
require.Equal(t, expectedItem, actualItem)

var (
actualItem2 ListItem
expectedItem2 = stringItem("\t\ttest\t\t")
)

require.NoError(t, p.ParseString("", `"\t\ttest\t\t"`, &actualItem2))
require.Equal(t, expectedItem2, actualItem2)
}

0 comments on commit fe65687

Please sign in to comment.