Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow custom & union productions #233

Merged
merged 3 commits into from Jun 13, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion context.go
Expand Up @@ -94,13 +94,15 @@ func (p *parseContext) Stop(err error, branch *parseContext) bool {
p.deepestError = err
p.deepestErrorDepth = maxInt(branch.PeekingLexer.Cursor(), branch.deepestErrorDepth)
}
if branch.PeekingLexer.Cursor() > p.PeekingLexer.Cursor()+p.lookahead {
if !p.hasInfiniteLookahead() && branch.PeekingLexer.Cursor() > p.PeekingLexer.Cursor()+p.lookahead {
mccolljr marked this conversation as resolved.
Show resolved Hide resolved
p.Accept(branch)
return true
}
return false
}

func (p *parseContext) hasInfiniteLookahead() bool { return p.lookahead < 0 }

func maxInt(a, b int) int {
if a > b {
return a
Expand Down
22 changes: 22 additions & 0 deletions ebnf.go
Expand Up @@ -51,6 +51,28 @@ func buildEBNF(root bool, n node, seen map[node]bool, p *ebnfp, outp *[]*ebnfp)
p.out += ")"
}

case *union:
mccolljr marked this conversation as resolved.
Show resolved Hide resolved
name := strings.ToUpper(n.typ.Name()[:1]) + n.typ.Name()[1:]
if p != nil {
p.out += name
}
if seen[n] {
return
}
p = &ebnfp{name: name}
*outp = append(*outp, p)
seen[n] = true
for i, next := range n.members {
if i > 0 {
p.out += " | "
}
buildEBNF(false, next, seen, p, outp)
}

case *custom:
name := strings.ToUpper(n.typ.Name()[:1]) + n.typ.Name()[1:]
p.out += name

case *strct:
name := strings.ToUpper(n.typ.Name()[:1]) + n.typ.Name()[1:]
if p != nil {
Expand Down
32 changes: 32 additions & 0 deletions grammar.go
Expand Up @@ -22,6 +22,38 @@ func newGeneratorContext(lex lexer.Definition) *generatorContext {
}
}

func (g *generatorContext) addUnionDefs(defs []unionDef) error {
unionNodes := make([]*union, len(defs))
for i, def := range defs {
mccolljr marked this conversation as resolved.
Show resolved Hide resolved
if _, exists := g.typeNodes[def.typ]; exists {
return fmt.Errorf("duplicate definition for interface or union type %s", def.typ)
}
unionNode := &union{def.typ, make([]node, 0, len(def.members))}
g.typeNodes[def.typ], unionNodes[i] = unionNode, unionNode
}
for i, def := range defs {
unionNode := unionNodes[i]
for _, memberType := range def.members {
memberNode, err := g.parseType(memberType)
if err != nil {
return err
}
unionNode.members = append(unionNode.members, memberNode)
}
}
return nil
}

func (g *generatorContext) addCustomDefs(defs []customDef) error {
for _, def := range defs {
if _, exists := g.typeNodes[def.typ]; exists {
return fmt.Errorf("duplicate definition for interface or union type %s", def.typ)
}
g.typeNodes[def.typ] = &custom{typ: def.typ, parseFn: def.parseFn}
}
return nil
}

// Takes a type and builds a tree of nodes out of it.
func (g *generatorContext) parseType(t reflect.Type) (_ node, returnedError error) {
t = indirectType(t)
Expand Down
43 changes: 42 additions & 1 deletion nodes.go
Expand Up @@ -72,6 +72,47 @@ func (p *parseable) Parse(ctx *parseContext, parent reflect.Value) (out []reflec
return []reflect.Value{rv.Elem()}, nil
}

// @@ (but for a custom production)
type custom struct {
typ reflect.Type
parseFn reflect.Value
}

func (c *custom) String() string { return ebnf(c) }
func (c *custom) GoString() string { return c.typ.Name() }

func (c *custom) Parse(ctx *parseContext, parent reflect.Value) (out []reflect.Value, err error) {
results := c.parseFn.Call([]reflect.Value{reflect.ValueOf(ctx.PeekingLexer)})
if err, _ := results[1].Interface().(error); err != nil {
if err == NextMatch {
return nil, nil
}
return nil, err
}
return []reflect.Value{results[0]}, nil
}

// @@ (for a union)
type union struct {
typ reflect.Type
members []node
}

func (u *union) String() string { return ebnf(u) }
func (u *union) GoString() string { return u.typ.Name() }

func (u *union) Parse(ctx *parseContext, parent reflect.Value) (out []reflect.Value, err error) {
temp := disjunction{u.members}
vals, err := temp.Parse(ctx, parent)
if err != nil {
return nil, err
}
for i := range vals {
vals[i] = vals[i].Convert(u.typ)
}
return vals, nil
}

// @@
type strct struct {
typ reflect.Type
Expand Down Expand Up @@ -710,7 +751,7 @@ func setField(tokens []lexer.Token, strct reflect.Value, field structLexerField,
f.Set(fv)
}

case reflect.Bool, reflect.Struct:
case reflect.Bool, reflect.Struct, reflect.Interface:
if f.Kind() == reflect.Bool && fv.Kind() == reflect.Bool {
f.SetBool(fv.Bool())
break
Expand Down
49 changes: 49 additions & 0 deletions options.go
@@ -1,6 +1,9 @@
package participle

import (
"fmt"
"reflect"

"github.com/alecthomas/participle/v2/lexer"
)

Expand All @@ -21,6 +24,10 @@ func Lexer(def lexer.Definition) Option {
//
// Note that increasing lookahead has a minor performance impact, but also
// reduces the accuracy of error reporting.
//
// If "n" is negative, this will be treated as "infinite lookahead".
mccolljr marked this conversation as resolved.
Show resolved Hide resolved
// This _will_ impact performance, but can be useful for parsing ambiguous
// grammars.
func UseLookahead(n int) Option {
return func(p *Parser) error {
p.useLookahead = n
Expand All @@ -41,6 +48,48 @@ func CaseInsensitive(tokens ...string) Option {
}
}

func UseCustom(parseFn interface{}) Option {
mccolljr marked this conversation as resolved.
Show resolved Hide resolved
errorType := reflect.TypeOf((*error)(nil)).Elem()
peekingLexerType := reflect.TypeOf((*lexer.PeekingLexer)(nil))
return func(p *Parser) error {
parseFnVal := reflect.ValueOf(parseFn)
parseFnType := parseFnVal.Type()
if parseFnType.Kind() != reflect.Func {
return fmt.Errorf("production parser must be a function (got %s)", parseFnType)
}
if parseFnType.NumIn() != 1 || parseFnType.In(0) != reflect.TypeOf((*lexer.PeekingLexer)(nil)) {
return fmt.Errorf("production parser must take a single parameter of type %s", peekingLexerType)
}
if parseFnType.NumOut() != 2 {
return fmt.Errorf("production parser must return exactly two values: the parsed production, and an error")
}
if parseFnType.Out(0).Kind() != reflect.Interface {
return fmt.Errorf("production parser's first return must be an interface type")
mccolljr marked this conversation as resolved.
Show resolved Hide resolved
}
if parseFnType.Out(1) != errorType {
return fmt.Errorf("production parser's second return must be %s", errorType)
}
prodType := parseFnType.Out(0)
p.customDefs = append(p.customDefs, customDef{prodType, parseFnVal})
return nil
}
}

func UseUnion[T any](members ...T) Option {
mccolljr marked this conversation as resolved.
Show resolved Hide resolved
return func(p *Parser) error {
unionType := reflect.TypeOf((*T)(nil)).Elem()
if unionType.Kind() != reflect.Interface {
return fmt.Errorf("union type must be an interface (got %s)", unionType)
}
memberTypes := make([]reflect.Type, 0, len(members))
for _, m := range members {
memberTypes = append(memberTypes, reflect.TypeOf(m))
}
p.unionDefs = append(p.unionDefs, unionDef{unionType, memberTypes})
return nil
}
}

// ParseOption modifies how an individual parse is applied.
type ParseOption func(p *parseContext)

Expand Down
19 changes: 19 additions & 0 deletions parser.go
Expand Up @@ -10,6 +10,16 @@ import (
"github.com/alecthomas/participle/v2/lexer"
)

type unionDef struct {
typ reflect.Type
members []reflect.Type
}

type customDef struct {
typ reflect.Type
parseFn reflect.Value
}

// A Parser for a particular grammar and lexer.
type Parser struct {
root node
Expand All @@ -19,6 +29,8 @@ type Parser struct {
useLookahead int
caseInsensitive map[string]bool
mappers []mapperByToken
unionDefs []unionDef
customDefs []customDef
elide []string
}

Expand Down Expand Up @@ -83,6 +95,13 @@ func Build(grammar interface{}, options ...Option) (parser *Parser, err error) {
}

context := newGeneratorContext(p.lex)
if err := context.addCustomDefs(p.customDefs); err != nil {
return nil, err
}
if err := context.addUnionDefs(p.unionDefs); err != nil {
return nil, err
}

v := reflect.ValueOf(grammar)
if v.Kind() == reflect.Interface {
v = v.Elem()
Expand Down