Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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

Issue with a nested structure #381

Closed
gwd opened this issue Nov 27, 2023 · 1 comment
Closed

Issue with a nested structure #381

gwd opened this issue Nov 27, 2023 · 1 comment

Comments

@gwd
Copy link

gwd commented Nov 27, 2023

First, thank you so much for this library -- made my first grammar on Friday and I think the setup really helped make things straightforward.

I'm using participle v2.10.0, and trying to write a parser for strings like the following:

	tests := []string{
		"Xen 4.18.x",
		"Linux 4.7.x",
		"QEMU 4.7-RC series",
		"Linux 4.7.x - Linux 4.9.x",
		"xen-unstable",
		"Xen 4.18.x - xen-unstable",
		"QEMU 4.7-RC series - QEMU 4.10.x",
	}

i.e., a version that may be a single version, or a range (separated by -); that may be a single string, or <project> <version number>.

I wrote the following participle structures:

type ProjectVersion struct {
	Project string `parser:"@Project"`
	Version string `parser:"Space @(VersionString | RCVersionString)"`
}

type VersionSingle struct {
	TipVersion     *string         `parser:"@TipVersion"`
	ProjectVersion *ProjectVersion `parser:"| @@"`
}

type VersionRange struct {
	From VersionSingle `parser:"@@"`
	To   VersionSingle `parser:"VersionDash @@"`
}

type Version struct {
	Range  *VersionRange  `parser:"@@"`
	Single *VersionSingle `parser:"| @@"`
}

var rulesCommon []lexer.SimpleRule{	
	{"Project", `Xen|Linux|QEMU|xapi`},
	{"TipVersion", `xen-unstable`},
	{"RCVersionString", `\d+\.\d+-RC series`},
	{"VersionString", `\d+\.\d+\.x`},
	{"VersionDash", " - "},
	{"Space", ` `},
}

Unfortunately, I get errors like the following:

Parsing Xen 4.18.x: 1:11: unexpected token "<EOF>" (expected <versiondash> VersionSingle)
Parsing Linux 4.7.x: 1:12: unexpected token "<EOF>" (expected <versiondash> VersionSingle)
Parsing QEMU 4.7-RC series: 1:19: unexpected token "<EOF>" (expected <versiondash> VersionSingle)

In other words, it's somehow getting stuck on parsing something as a VersionRange, and not backing out and parsing it simply as a VersionSingle.

But this only happens if both Version and VersionSingle have at least two ways to be interpreted. If I replace the ProjectVersion with a single regexp that matches the same string, it works (here replacing the Version lexer token with a ProjectVersion token with the appropriate regexp).

// With the range, without the "project version"
type VersionSingleNoProjectVersion struct {
	TipVersion     *string `parser:"@TipVersion"`
	ProjectVersion *string `parser:"| @ProjectVersion"`
}

type VersionRangeNoProjectVersion struct {
	From VersionSingleNoProjectVersion `parser:"@@"`
	To   VersionSingleNoProjectVersion `parser:"VersionDash @@"`
}

type VersionNoProjectVersion struct {
	Range  *VersionRangeNoProjectVersion  `parser:"@@"`
	Single *VersionSingleNoProjectVersion `parser:"| @@"`
}

The problem for single items goes away if I get rid of the RangeVersion; but then of course you can't parse ranges:

// With "project version", without range
type VersionNoRange struct {
	Single *VersionSingle `parser:"@@"`
}

Any idea what's going on?

For completeness, here's a complete testing function you can use to trigger the issue:

package participletest_test

import (
	"testing"

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

// What I'd like:
type ProjectVersion struct {
	Project string `parser:"@Project"`
	Version string `parser:"Space @(VersionString | RCVersionString)"`
}

type VersionSingle struct {
	TipVersion     *string         `parser:"@TipVersion"`
	ProjectVersion *ProjectVersion `parser:"| @@"`
}

type VersionRange struct {
	From VersionSingle `parser:"@@"`
	To   VersionSingle `parser:"VersionDash @@"`
}

type Version struct {
	Range  *VersionRange  `parser:"@@"`
	Single *VersionSingle `parser:"| @@"`
}

// With "project version", without range
type VersionNoRange struct {
	Single *VersionSingle `parser:"@@"`
}

// With the range, without the "project version"
type VersionSingleNoProjectVersion struct {
	TipVersion     *string `parser:"@TipVersion"`
	ProjectVersion *string `parser:"| @ProjectVersion"`
}

type VersionRangeNoProjectVersion struct {
	From VersionSingleNoProjectVersion `parser:"@@"`
	To   VersionSingleNoProjectVersion `parser:"VersionDash @@"`
}

type VersionNoProjectVersion struct {
	Range  *VersionRangeNoProjectVersion  `parser:"@@"`
	Single *VersionSingleNoProjectVersion `parser:"| @@"`
}

var rulesCommon = []lexer.SimpleRule{
	{"TipVersion", `xen-unstable`},
	{"RCVersionString", `\d+\.\d+-RC series`},
	{"VersionString", `\d+\.\d+\.x`},
	{"VersionDash", " - "},
	{"Space", ` `},
}

func TestVersion(t *testing.T) {
	simpletests := []string{
		"Xen 4.18.x",
		"Linux 4.7.x",
		"QEMU 4.7-RC series",
		"xen-unstable",
	}

	rangetests := []string{
		"Linux 4.7.x - Linux 4.9.x",
		"Xen 4.18.x - xen-unstable",
		"QEMU 4.7-RC series - QEMU 4.10.x",
	}

	lexProject := lexer.MustSimple(append(rulesCommon, lexer.SimpleRule{"Project", `Xen|Linux|QEMU|xapi`}))

	lexProjectVersion := lexer.MustSimple(append(rulesCommon,
		lexer.SimpleRule{"ProjectVersion", `(Xen|Linux|QEMU|xapi) (\d+\.\d+\.x|\d+\.\d+-RC series)`}))

	pVersion := participle.MustBuild[Version](participle.Lexer(lexProject))

	t.Log("Testing pVersion with simple and range")
	for _, in := range append(simpletests, rangetests...) {
		out, err := pVersion.ParseString("", in)
		if err != nil {
			t.Errorf("ERROR: Parsing %v: %v", in, err)
		} else {
			t.Logf("Parsing %v resulted in %v", in, out)
		}
	}

	pVersionNoRange := participle.MustBuild[VersionNoRange](participle.Lexer(lexProject))

	t.Log("Testing pVersionNoRange with simple only")
	for _, in := range simpletests {
		out, err := pVersionNoRange.ParseString("", in)
		if err != nil {
			t.Errorf("ERROR: Parsing %v: %v", in, err)
		} else {
			t.Logf("Parsing %v resulted in %v", in, out)
		}
	}

	pVersionNoProjcetVersion := participle.MustBuild[VersionNoProjectVersion](participle.Lexer(lexProjectVersion))

	t.Log("Testing pVersionNoProjectVersion with simple and range")
	for _, in := range append(simpletests, rangetests...) {
		out, err := pVersionNoProjcetVersion.ParseString("", in)
		if err != nil {
			t.Errorf("ERROR: Parsing %v: %v", in, err)
		} else {
			t.Logf("Parsing %v resulted in %v", in, out)
		}
	}
}
@gwd
Copy link
Author

gwd commented Nov 27, 2023

BTW, the following works for all tests:

type VersionSlice struct {
	Range []VersionSingle `parser:"@@ ( VersionDash @@ )*"`
}

...
	pVersionSlice := participle.MustBuild[VersionSlice](participle.Lexer(lexProject))

	t.Log("Testing pVersionSlice with simple and range")
	for _, in := range append(simpletests, rangetests...) {
		out, err := pVersionSlice.ParseString("", in)
		if err != nil {
			t.Errorf("ERROR: Parsing %v: %v", in, err)
		} else {
			t.Logf("Parsing %v resulted in %v", in, out)
		}
	}
...

So I think I have a work-around for now; but if the first version is supposed to work, it would be good to track down what's going on.

Repository owner locked and limited conversation to collaborators Apr 27, 2024
@alecthomas alecthomas converted this issue into discussion #400 Apr 27, 2024

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant