alecthomas / participle

A parser library for Go

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Matching `Union` type into array

svex99 opened this issue · comments

I am trying to parse a configuration file for Bind9 DNS server. This file contains a different number of records, like NS, A, MX, etc. The order in which they are defined is not relevant. In order, I want to parse them into an array of type Record.

Every record has different fields, so I defined them in separate structs and used participle.Union[Record](...) to conform the union expression.

The problem is when trying to store them in an array []*Record with the tag @@*. After parsed, the array is always nil.

I tried to parse a simple record with the union type, and it works, so I don't understand why the array behaves different and is always nil. I guess it is related to the type system of Go, but not sure.

Any possible explanation is welcome. My final question is wow can I parse the different records?

Thanks in advance!

Here, an example of the file to parse:

$ORIGIN example.com.
$TTL 2d

@ IN SOA ns1 admin (
    1
    2
    3
    4
    5
)

@ IN NS ns1

ns1 IN A 10.10.10.10

@ IN MX 100 email

email IN A 20.20.20.20

And my code:

package main

import (
	"os"

	"github.com/alecthomas/repr"

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

var (
	domainConfLexer = lexer.MustSimple([]lexer.SimpleRule{
		{Name: "Directive", Pattern: `\$(ORIGIN|TTL)`},
		{Name: "Keyword", Pattern: `@|IN|SOA|NS|A|MX|TXT|PTR`},
		{Name: "Domain", Pattern: `[a-zA-Z][\w\-]*\.[a-zA-Z]+`},
		{Name: "Name", Pattern: `[a-zA-Z][\w\-]*`},
		{Name: "Ttl", Pattern: `\d+[hdw]`},
		{Name: "Ipv4", Pattern: `\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}`},
		{Name: "Uint", Pattern: `\d+`},
		{Name: "Punct", Pattern: `[\.\(\)]`},
		{Name: "Comment", Pattern: `;[^\n]*`},
		{Name: "Whitespace", Pattern: `[ \n\t\r]+`},
	})
	parser = participle.MustBuild[DomainConf](
		participle.Lexer(domainConfLexer),
		participle.Union[Record](NSRecord{}, ARecord{}, MXRecord{}),
		participle.Elide("Whitespace", "Comment"),
		participle.UseLookahead(2),
	)
)

type DomainConf struct {
	Origin    string     `parser:"'$ORIGIN' @Domain '.'"`
	Ttl       string     `parser:"'$TTL' @Ttl"`
	SOARecord *SOARecord `parser:"@@"`
	Records   []*Record  `parser:"@@*"`
}

type Record interface{ value() }

type SOARecord struct {
	NameServer string `parser:"'@' 'IN' 'SOA' @Name"`
	Admin      string `parser:"@Name"`
	Serial     uint   `parser:"'(' @Uint"`
	Refresh    uint   `parser:"@Uint"`
	Retry      uint   `parser:"@Uint"`
	Expire     uint   `parser:"@Uint"`
	Minimum    uint   `parser:"@Uint ')'"`
}

type NSRecord struct {
	NameServer string `parser:"'@' 'IN' 'NS' @Name"`
}

func (NSRecord) value() {}

type ARecord struct {
	Name string `parser:"@Name"`
	Ip   string `parser:"'IN' 'A' @Ipv4"`
}

func (ARecord) value() {}

type MXRecord struct {
	Priority    uint   `parser:"'@' 'IN' 'MX' @Uint"`
	EmailServer string `parser:"@Name"`
}

func (MXRecord) value() {}

func main() {
	reader, err := os.Open("test.txt")
	if err != nil {
		panic(err)
	}

	dConf, err := parser.Parse("", reader)
	if err != nil {
		panic(err)
	}

	repr.Println(dConf, repr.Indent("  "), repr.OmitEmpty(false))
}

The output after execute go run . is:

&main.DomainConf{
  Origin: "example.com",
  Ttl: "2d",
  SOARecord: &main.SOARecord{
    NameServer: "ns1",
    Admin: "admin",
    Serial: 1,
    Refresh: 2,
    Retry: 3,
    Expire: 4,
    Minimum: 5,
  },
  Records: nil,
}

The problem is that you're trying parse into a pointer to an interface rather than just into the interface.

Change []*Record to []Record. Here it is on the Go playground.