From bf4573a484dd4b29230932432205d6a99988d5ec Mon Sep 17 00:00:00 2001 From: Alec Thomas Date: Mon, 27 Jun 2022 11:13:47 +1000 Subject: [PATCH] Genericise the parser. `Build(&Grammar{})` becomes `Build[Grammar]()` and `err = parser.Parse(filename, &grammar)` becomes `grammar, err := parser.Parse(filename)`. --- .golangci.yml | 1 + README.md | 12 +- TUTORIAL.md | 9 +- _examples/basic/ast.go | 3 +- _examples/basic/main.go | 2 +- _examples/ebnf/main.go | 5 +- _examples/ebnf/main_test.go | 5 +- _examples/expr/main.go | 5 +- _examples/expr/main_test.go | 5 +- _examples/expr2/main.go | 5 +- _examples/expr2/main_test.go | 5 +- _examples/expr3/main.go | 5 +- _examples/expr3/main_test.go | 4 +- _examples/expr4/main.go | 5 +- _examples/expr4/main_test.go | 4 +- _examples/generics/main.go | 7 +- _examples/graphql/main.go | 5 +- _examples/graphql/main_test.go | 3 +- _examples/hcl/main.go | 5 +- _examples/hcl/main_test.go | 7 +- _examples/ini/main.go | 7 +- _examples/ini/main_test.go | 5 +- _examples/jsonpath/main.go | 8 +- _examples/jsonpath/main_test.go | 3 +- _examples/microc/main.go | 9 +- _examples/microc/main_test.go | 6 +- _examples/precedenceclimbing/main.go | 9 +- _examples/precedenceclimbing/main_test.go | 3 +- _examples/protobuf/main.go | 5 +- _examples/protobuf/main_test.go | 5 +- _examples/simpleexpr/main.go | 5 +- _examples/simpleexpr/main_test.go | 5 +- _examples/sql/main.go | 6 +- _examples/sql/main_test.go | 3 +- _examples/stateful/main.go | 7 +- _examples/stateful/main_test.go | 3 +- _examples/thrift/main.go | 5 +- _examples/thrift/main_test.go | 14 +- _examples/toml/main.go | 9 +- _examples/toml/main_test.go | 5 +- ebnf.go | 2 +- ebnf/ebnf.go | 8 +- ebnf_test.go | 6 +- error_test.go | 12 +- grammar_test.go | 12 +- lexer/stateful_test.go | 19 +- lexer/text_scanner_test.go | 10 +- lookahead_test.go | 155 +++--- map.go | 4 +- map_test.go | 8 +- options.go | 12 +- parser.go | 92 ++-- parser_test.go | 620 ++++++++++------------ validate_test.go | 4 +- 54 files changed, 543 insertions(+), 645 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index f1c60ffb..5229d875 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -50,6 +50,7 @@ linters: - errname - nilnil - maintidx + - unused # Does not work with type parameters linters-settings: govet: diff --git a/README.md b/README.md index 1b16eefb..2aa94f48 100644 --- a/README.md +++ b/README.md @@ -122,14 +122,13 @@ parser from the tutorial. A parser is constructed from a grammar and a lexer: ```go -parser, err := participle.Build(&INI{}) +parser, err := participle.Build[INI]() ``` Once constructed, the parser is applied to input to produce an AST: ```go -ast := &INI{} -err := parser.ParseString("", "size = 10", ast) +ast, err := parser.ParseString("", "size = 10") // ast == &INI{ // Properties: []*Property{ // {Key: "size", Value: &Value{Int: &10}}, @@ -282,7 +281,7 @@ now supports this pattern. Simply construct your parser with the `Union[T](membe option, eg. ```go -parser := participle.MustBuild(&AST{}, participle.Union[Value](Float{}, Int{}, String{}, Bool{})) +parser := participle.MustBuild[AST](participle.Union[Value](Float{}, Int{}, String{}, Bool{})) ``` Custom parsers may also be defined for union types with the [ParseTypeWith](https://pkg.go.dev/github.com/alecthomas/participle/v2#ParseTypeWith) option. @@ -516,7 +515,7 @@ var ( {"Punct", `[-[!@#$%^&*()+_={}\|:;"'<,>.?/]|]`, nil}, {"Whitespace", `[ \t\n\r]+`, nil}, }) - parser = participle.MustBuild(&File{}, + parser = participle.MustBuild[File]( participle.Lexer(graphQLLexer), participle.Elide("Comment", "Whitespace"), participle.UseLookahead(2), @@ -535,10 +534,9 @@ func main() { ctx.Exit(0) } for _, file := range cli.Files { - ast := &File{} r, err := os.Open(file) ctx.FatalIfErrorf(err) - err = parser.Parse(file, r, ast) + ast, err := parser.Parse(file, r) r.Close() repr.Println(ast) ctx.FatalIfErrorf(err) diff --git a/TUTORIAL.md b/TUTORIAL.md index 90a9810f..e7e7cc81 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -252,21 +252,20 @@ To parse with this grammar we first construct the parser (we'll use the default lexer for now): ```go -parser, err := participle.Build(&INI{}) +parser, err := participle.Build[INI]() ``` -Then create a root node and parse into it with `parser.Parse{,String,Bytes}()`: +Then parse a new INI file with `parser.Parse{,String,Bytes}()`: ```go -ini := &INI{} -err = parser.ParseString("", ` +ini, err := parser.ParseString("", ` age = 21 name = "Bob Smith" [address] city = "Beverly Hills" postal_code = 90210 -`, ini) +`) ``` You can find the full example [here](_examples/ini/main.go), alongside diff --git a/_examples/basic/ast.go b/_examples/basic/ast.go index b6d711f4..5762fcc1 100644 --- a/_examples/basic/ast.go +++ b/_examples/basic/ast.go @@ -10,8 +10,7 @@ import ( // Parse a BASIC program. func Parse(r io.Reader) (*Program, error) { - program := &Program{} - err := basicParser.Parse("", r, program) + program, err := basicParser.Parse("", r) if err != nil { return nil, err } diff --git a/_examples/basic/main.go b/_examples/basic/main.go index 416fdc95..9d376ce5 100644 --- a/_examples/basic/main.go +++ b/_examples/basic/main.go @@ -21,7 +21,7 @@ var ( {"whitespace", `[ \t]+`}, }) - basicParser = participle.MustBuild(&Program{}, + basicParser = participle.MustBuild[Program]( participle.Lexer(basicLexer), participle.CaseInsensitive("Ident"), participle.Unquote("String"), diff --git a/_examples/ebnf/main.go b/_examples/ebnf/main.go index 8341c1f3..ef73bdd9 100644 --- a/_examples/ebnf/main.go +++ b/_examples/ebnf/main.go @@ -135,7 +135,7 @@ func (e *EBNF) String() string { return w.String() } -var parser = participle.MustBuild(&EBNF{}) +var parser = participle.MustBuild[EBNF]() func main() { help := `An EBNF parser compatible with Go"s exp/ebnf. The grammar is @@ -151,8 +151,7 @@ in the form: ` ctx := kong.Parse(&cli, kong.Description(help)) - ebnf := &EBNF{} - err := parser.Parse("", os.Stdin, ebnf) + ebnf, err := parser.Parse("", os.Stdin) ctx.FatalIfErrorf(err, "") if cli.JSON { diff --git a/_examples/ebnf/main_test.go b/_examples/ebnf/main_test.go index f28237de..70d992fb 100644 --- a/_examples/ebnf/main_test.go +++ b/_examples/ebnf/main_test.go @@ -7,14 +7,13 @@ import ( ) func TestExe(t *testing.T) { - ast := &EBNF{} - err := parser.ParseString("", ` + _, err := parser.ParseString("", ` Production = name "=" [ Expression ] "." . Expression = Alternative { "|" Alternative } . Alternative = Term { Term } . Term = name | token [ "…" token ] | Group | Option | Repetition . Group = "(" Expression ")" . Option = "[" Expression "]" . - Repetition = "{" Expression "}" .`, ast) + Repetition = "{" Expression "}" .`) require.NoError(t, err) } diff --git a/_examples/expr/main.go b/_examples/expr/main.go index 92ecee20..87d83028 100644 --- a/_examples/expr/main.go +++ b/_examples/expr/main.go @@ -186,7 +186,7 @@ func (e *Expression) Eval(ctx Context) float64 { type Context map[string]float64 -var parser = participle.MustBuild(&Expression{}) +var parser = participle.MustBuild[Expression]() func main() { ctx := kong.Parse(&cli, @@ -194,8 +194,7 @@ func main() { kong.UsageOnError(), ) - expr := &Expression{} - err := parser.ParseString("", strings.Join(cli.Expression, " "), expr) + expr, err := parser.ParseString("", strings.Join(cli.Expression, " ")) ctx.FatalIfErrorf(err) if cli.AST { diff --git a/_examples/expr/main_test.go b/_examples/expr/main_test.go index 7b49d733..3df87609 100644 --- a/_examples/expr/main_test.go +++ b/_examples/expr/main_test.go @@ -8,8 +8,7 @@ import ( ) func TestExe(t *testing.T) { - expr := &Expression{} - err := parser.ParseString("", `1 + 2 / 3 * (1 + 2)`, expr) - require.NoError(t, err) + expr, err := parser.ParseString("", `1 + 2 / 3 * (1 + 2)`) repr.Println(expr) + require.NoError(t, err) } diff --git a/_examples/expr2/main.go b/_examples/expr2/main.go index fca58188..a633a24f 100644 --- a/_examples/expr2/main.go +++ b/_examples/expr2/main.go @@ -63,7 +63,7 @@ type Primary struct { SubExpression *Expression `| "(" @@ ")" ` } -var parser = participle.MustBuild(&Expression{}, participle.UseLookahead(2)) +var parser = participle.MustBuild[Expression](participle.UseLookahead(2)) func main() { var cli struct { @@ -71,8 +71,7 @@ func main() { } ctx := kong.Parse(&cli) - expr := &Expression{} - err := parser.ParseString("", strings.Join(cli.Expr, " "), expr) + expr, err := parser.ParseString("", strings.Join(cli.Expr, " ")) ctx.FatalIfErrorf(err) repr.Println(expr) diff --git a/_examples/expr2/main_test.go b/_examples/expr2/main_test.go index 7b49d733..3df87609 100644 --- a/_examples/expr2/main_test.go +++ b/_examples/expr2/main_test.go @@ -8,8 +8,7 @@ import ( ) func TestExe(t *testing.T) { - expr := &Expression{} - err := parser.ParseString("", `1 + 2 / 3 * (1 + 2)`, expr) - require.NoError(t, err) + expr, err := parser.ParseString("", `1 + 2 / 3 * (1 + 2)`) repr.Println(expr) + require.NoError(t, err) } diff --git a/_examples/expr3/main.go b/_examples/expr3/main.go index 9f3657d0..7e5b5aae 100644 --- a/_examples/expr3/main.go +++ b/_examples/expr3/main.go @@ -104,7 +104,7 @@ type Expression struct { X ExprPrecAll `@@` } -var parser = participle.MustBuild(&Expression{}, +var parser = participle.MustBuild[Expression]( // This grammar requires enough lookahead to see the entire expression before // it can select the proper binary expression type - in other words, we only // know that `1 * 2 * 3 * 4` isn't the left-hand side of an addition or subtraction @@ -126,8 +126,7 @@ func main() { } ctx := kong.Parse(&cli) - expr := &Expression{} - err := parser.ParseString("", strings.Join(cli.Expr, " "), expr) + expr, err := parser.ParseString("", strings.Join(cli.Expr, " ")) ctx.FatalIfErrorf(err) repr.Println(expr) diff --git a/_examples/expr3/main_test.go b/_examples/expr3/main_test.go index 8e2e2e97..0f8df8fb 100644 --- a/_examples/expr3/main_test.go +++ b/_examples/expr3/main_test.go @@ -42,8 +42,8 @@ func TestExpressionParser(t *testing.T) { }, }, } { - var actual Expression - require.NoError(t, parser.ParseString("", c.src, &actual)) + actual, err := parser.ParseString("", c.src) + require.NoError(t, err) require.Equal(t, c.expected, actual.X) } } diff --git a/_examples/expr4/main.go b/_examples/expr4/main.go index 450f1652..8da442c5 100644 --- a/_examples/expr4/main.go +++ b/_examples/expr4/main.go @@ -120,7 +120,7 @@ type Expression struct { X Expr `@@` } -var parser = participle.MustBuild(&Expression{}, participle.ParseTypeWith(parseExprAny)) +var parser = participle.MustBuild[Expression](participle.ParseTypeWith(parseExprAny)) func main() { var cli struct { @@ -128,8 +128,7 @@ func main() { } ctx := kong.Parse(&cli) - expr := &Expression{} - err := parser.ParseString("", strings.Join(cli.Expr, " "), expr) + expr, err := parser.ParseString("", strings.Join(cli.Expr, " ")) ctx.FatalIfErrorf(err) repr.Println(expr) diff --git a/_examples/expr4/main_test.go b/_examples/expr4/main_test.go index e2444920..35fe950a 100644 --- a/_examples/expr4/main_test.go +++ b/_examples/expr4/main_test.go @@ -54,8 +54,8 @@ func TestCustomExprParser(t *testing.T) { }, }, } { - var actual Expression - require.NoError(t, parser.ParseString("", c.src, &actual)) + actual, err := parser.ParseString("", c.src) + require.NoError(t, err) require.Equal(t, c.expected, actual.X) } } diff --git a/_examples/generics/main.go b/_examples/generics/main.go index 6da5beeb..3272b865 100644 --- a/_examples/generics/main.go +++ b/_examples/generics/main.go @@ -35,16 +35,15 @@ type RHS struct { RHS *Expr `@@` } -var parser = participle.MustBuild(&Expr{}, participle.UseLookahead(1024)) +var parser = participle.MustBuild[Expr](participle.UseLookahead(1024)) func main() { - expr := &Expr{} - err := parser.ParseString("", "hello < world * (1 + 3) && (world > 10)", expr) + expr, err := parser.ParseString("", "hello < world * (1 + 3) && (world > 10)") if err != nil { panic(err) } repr.Println(expr) - err = parser.ParseString("", "type.method(1, 2, 3)", expr) + expr, err = parser.ParseString("", "type.method(1, 2, 3)") if err != nil { panic(err) } diff --git a/_examples/graphql/main.go b/_examples/graphql/main.go index 65dbb271..42f5a372 100644 --- a/_examples/graphql/main.go +++ b/_examples/graphql/main.go @@ -68,7 +68,7 @@ var ( {"Punct", `[-[!@#$%^&*()+_={}\|:;"'<,>.?/]|]`}, {"Whitespace", `[ \t\n\r]+`}, }) - parser = participle.MustBuild(&File{}, + parser = participle.MustBuild[File]( participle.Lexer(graphQLLexer), participle.Elide("Comment", "Whitespace"), participle.UseLookahead(2), @@ -87,10 +87,9 @@ func main() { ctx.Exit(0) } for _, file := range cli.Files { - ast := &File{} r, err := os.Open(file) ctx.FatalIfErrorf(err) - err = parser.Parse("", r, ast) + ast, err := parser.Parse("", r) r.Close() repr.Println(ast) ctx.FatalIfErrorf(err) diff --git a/_examples/graphql/main_test.go b/_examples/graphql/main_test.go index 46775567..40383116 100644 --- a/_examples/graphql/main_test.go +++ b/_examples/graphql/main_test.go @@ -13,7 +13,6 @@ func BenchmarkParser(b *testing.B) { b.ReportAllocs() b.ReportMetric(float64(len(source)*b.N), "B/s") for i := 0; i < b.N; i++ { - ast := &File{} - _ = parser.ParseBytes("", source, ast) + _, _ = parser.ParseBytes("", source) } } diff --git a/_examples/hcl/main.go b/_examples/hcl/main.go index de20e02f..b1d84821 100644 --- a/_examples/hcl/main.go +++ b/_examples/hcl/main.go @@ -58,11 +58,10 @@ type Config struct { Entries []*Entry `@@*` } -var parser = participle.MustBuild(&Config{}, participle.Unquote()) +var parser = participle.MustBuild[Config](participle.Unquote()) func main() { - expr := &Config{} - err := parser.Parse("", os.Stdin, expr) + expr, err := parser.Parse("", os.Stdin) if err != nil { panic(err) } diff --git a/_examples/hcl/main_test.go b/_examples/hcl/main_test.go index c1524171..85f7828a 100644 --- a/_examples/hcl/main_test.go +++ b/_examples/hcl/main_test.go @@ -8,8 +8,7 @@ import ( ) func TestExe(t *testing.T) { - ast := &Config{} - err := parser.ParseString("", ` + ast, err := parser.ParseString("", ` region = "us-west-2" access_key = "something" secret_key = "something_else" @@ -35,7 +34,7 @@ directory data { pre_restore_script = "before_restore.sh" post_restore_script = "after_restore.sh" } -`, ast) - require.NoError(t, err) +`) repr.Println(ast) + require.NoError(t, err) } diff --git a/_examples/ini/main.go b/_examples/ini/main.go index 79442e31..be6ec9ef 100644 --- a/_examples/ini/main.go +++ b/_examples/ini/main.go @@ -20,7 +20,7 @@ var ( {"comment", `[#;][^\n]*`}, {"whitespace", `\s+`}, }) - parser = participle.MustBuild(&INI{}, + parser = participle.MustBuild[INI]( participle.Lexer(iniLexer), participle.Unquote("String"), participle.Union[Value](String{}, Number{}), @@ -57,10 +57,9 @@ type Number struct { func (Number) value() {} func main() { - ini := &INI{} - err := parser.Parse("", os.Stdin, ini) + ini, err := parser.Parse("", os.Stdin) + repr.Println(ini, repr.Indent(" "), repr.OmitEmpty(true)) if err != nil { panic(err) } - repr.Println(ini, repr.Indent(" "), repr.OmitEmpty(true)) } diff --git a/_examples/ini/main_test.go b/_examples/ini/main_test.go index 40df28a6..ae097840 100644 --- a/_examples/ini/main_test.go +++ b/_examples/ini/main_test.go @@ -8,13 +8,12 @@ import ( ) func TestExe(t *testing.T) { - ini := &INI{} - err := parser.ParseString("", ` + ini, err := parser.ParseString("", ` global = 1 [section] value = "str" -`, ini) +`) require.NoError(t, err) repr.Println(ini) } diff --git a/_examples/jsonpath/main.go b/_examples/jsonpath/main.go index 195be94f..55c142cb 100644 --- a/_examples/jsonpath/main.go +++ b/_examples/jsonpath/main.go @@ -22,7 +22,7 @@ type acc struct { Index *int `| @Int` } -var parser = participle.MustBuild(&pathExpr{}) +var parser = participle.MustBuild[pathExpr]() func main() { if len(os.Args) < 3 { @@ -33,8 +33,8 @@ func main() { q := os.Args[1] files := os.Args[2:] - var expr pathExpr - if err := parser.ParseString("", q, &expr); err != nil { + expr, err := parser.ParseString("", q) + if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } @@ -71,7 +71,7 @@ func main() { } } -func match(input map[string]interface{}, expr pathExpr) (interface{}, error) { +func match(input map[string]interface{}, expr *pathExpr) (interface{}, error) { var v interface{} = input for _, e := range expr.Parts { switch m := v.(type) { diff --git a/_examples/jsonpath/main_test.go b/_examples/jsonpath/main_test.go index 6b186873..d9389c10 100644 --- a/_examples/jsonpath/main_test.go +++ b/_examples/jsonpath/main_test.go @@ -15,8 +15,7 @@ func TestExe(t *testing.T) { err = json.NewDecoder(r).Decode(&input) require.NoError(t, err) - ast := pathExpr{} - err = parser.ParseString(``, `check_run.check_suite.pull_requests[0].url`, &ast) + ast, err := parser.ParseString(``, `check_run.check_suite.pull_requests[0].url`) require.NoError(t, err) result, err := match(input, ast) diff --git a/_examples/microc/main.go b/_examples/microc/main.go index f10af7ea..95111e43 100644 --- a/_examples/microc/main.go +++ b/_examples/microc/main.go @@ -236,7 +236,7 @@ var ( {"Punct", `[-,()*/+%{};&!=:<>]|\[|\]`}, {"Int", `\d+`}, }) - parser = participle.MustBuild(&Program{}, + parser = participle.MustBuild[Program]( participle.Lexer(lex), participle.UseLookahead(2)) ) @@ -278,11 +278,8 @@ int main(void) ` func main() { - ast := &Program{} - defer func() { - repr.Println(ast) - }() - err := parser.ParseString("", sample, ast) + ast, err := parser.ParseString("", sample) + repr.Println(ast) if err != nil { panic(err) } diff --git a/_examples/microc/main_test.go b/_examples/microc/main_test.go index f1254580..99c02183 100644 --- a/_examples/microc/main_test.go +++ b/_examples/microc/main_test.go @@ -9,8 +9,7 @@ import ( ) func TestExe(t *testing.T) { - program := &Program{} - err := parser.ParseString("", sample, program) + program, err := parser.ParseString("", sample) require.NoError(t, err) repr.Println(program) } @@ -20,7 +19,6 @@ func BenchmarkParser(b *testing.B) { b.ReportAllocs() b.ReportMetric(float64(len(src)*b.N), "B/s") for i := 0; i < b.N; i++ { - program := &Program{} - _ = parser.ParseString("", src, program) + _, _ = parser.ParseString("", src) } } diff --git a/_examples/precedenceclimbing/main.go b/_examples/precedenceclimbing/main.go index 088ba540..c98abf15 100644 --- a/_examples/precedenceclimbing/main.go +++ b/_examples/precedenceclimbing/main.go @@ -115,14 +115,13 @@ func parseOp(op string, lhs *Expr, rhs *Expr) *Expr { } } -var parser = participle.MustBuild(&Expr{}) +var parser = participle.MustBuild[Expr]() func main() { - e := &Expr{} - err := parser.ParseString("", strings.Join(os.Args[1:], " "), e) + e, err := parser.ParseString("", strings.Join(os.Args[1:], " ")) + fmt.Println(e) + repr.Println(e) if err != nil { panic(err) } - fmt.Println(e) - repr.Println(e) } diff --git a/_examples/precedenceclimbing/main_test.go b/_examples/precedenceclimbing/main_test.go index 4d35e568..93fe8ff8 100644 --- a/_examples/precedenceclimbing/main_test.go +++ b/_examples/precedenceclimbing/main_test.go @@ -7,8 +7,7 @@ import ( ) func TestExe(t *testing.T) { - actual := &Expr{} - err := parser.ParseString("", `1 + 2 - 3 * (4 + 2)`, actual) + actual, err := parser.ParseString("", `1 + 2 - 3 * (4 + 2)`) require.NoError(t, err) expected := expr( expr(intp(1), "+", intp(2)), diff --git a/_examples/protobuf/main.go b/_examples/protobuf/main.go index 8d2fccd2..9d7bd044 100644 --- a/_examples/protobuf/main.go +++ b/_examples/protobuf/main.go @@ -254,7 +254,7 @@ type MapType struct { } var ( - parser = participle.MustBuild(&Proto{}, participle.UseLookahead(2)) + parser = participle.MustBuild[Proto](participle.UseLookahead(2)) cli struct { Files []string `required existingfile arg help:"Protobuf files."` @@ -266,10 +266,9 @@ func main() { for _, file := range cli.Files { fmt.Println(file) - proto := &Proto{} r, err := os.Open(file) ctx.FatalIfErrorf(err, "") - err = parser.Parse("", r, proto) + proto, err := parser.Parse("", r) ctx.FatalIfErrorf(err, "") repr.Println(proto, repr.Hide(&lexer.Position{})) } diff --git a/_examples/protobuf/main_test.go b/_examples/protobuf/main_test.go index 78b4bfc8..93ad7065 100644 --- a/_examples/protobuf/main_test.go +++ b/_examples/protobuf/main_test.go @@ -7,8 +7,7 @@ import ( ) func TestExe(t *testing.T) { - ast := &Proto{} - err := parser.ParseString("", ` + _, err := parser.ParseString("", ` syntax = "proto3"; package test.test; @@ -38,6 +37,6 @@ enum Type { service SearchService { rpc Search(SearchRequest) returns (SearchResponse); } -`, ast) +`) require.NoError(t, err) } diff --git a/_examples/simpleexpr/main.go b/_examples/simpleexpr/main.go index 1be51c64..ff654670 100644 --- a/_examples/simpleexpr/main.go +++ b/_examples/simpleexpr/main.go @@ -29,7 +29,7 @@ var ( cli struct { Expr string `arg:"" help:"Expression."` } - parser = participle.MustBuild(&Expr{}) + parser = participle.MustBuild[Expr]() ) func main() { @@ -42,8 +42,7 @@ the parser, is that it is significantly less complex and less nested. The advantage of this over the "precedenceclimbing" example is that no custom parsing is required. `)) - expr := &Expr{} - err := parser.ParseString("", cli.Expr, expr) + expr, err := parser.ParseString("", cli.Expr) kctx.FatalIfErrorf(err) repr.Println(expr) } diff --git a/_examples/simpleexpr/main_test.go b/_examples/simpleexpr/main_test.go index d89a02dd..3df87609 100644 --- a/_examples/simpleexpr/main_test.go +++ b/_examples/simpleexpr/main_test.go @@ -8,8 +8,7 @@ import ( ) func TestExe(t *testing.T) { - expr := &Expr{} - err := parser.ParseString("", `1 + 2 / 3 * (1 + 2)`, expr) - require.NoError(t, err) + expr, err := parser.ParseString("", `1 + 2 / 3 * (1 + 2)`) repr.Println(expr) + require.NoError(t, err) } diff --git a/_examples/sql/main.go b/_examples/sql/main.go index 64fb4edb..b6b2aebd 100644 --- a/_examples/sql/main.go +++ b/_examples/sql/main.go @@ -166,8 +166,7 @@ var ( {`Operators`, `<>|!=|<=|>=|[-+*/%,.()=<>]`}, {"whitespace", `\s+`}, }) - parser = participle.MustBuild( - &Select{}, + parser = participle.MustBuild[Select]( participle.Lexer(sqlLexer), participle.Unquote("String"), participle.CaseInsensitive("Keyword"), @@ -179,8 +178,7 @@ var ( func main() { ctx := kong.Parse(&cli) - sql := &Select{} - err := parser.ParseString("", cli.SQL, sql) + sql, err := parser.ParseString("", cli.SQL) repr.Println(sql, repr.Indent(" "), repr.OmitEmpty(true)) ctx.FatalIfErrorf(err) } diff --git a/_examples/sql/main_test.go b/_examples/sql/main_test.go index 4c6b86c2..f5531179 100644 --- a/_examples/sql/main_test.go +++ b/_examples/sql/main_test.go @@ -8,8 +8,7 @@ import ( ) func TestExe(t *testing.T) { - sel := &Select{} - err := parser.ParseString("", `SELECT * FROM table WHERE attr = 10`, sel) + sel, err := parser.ParseString("", `SELECT * FROM table WHERE attr = 10`) require.NoError(t, err) repr.Println(sel) } diff --git a/_examples/stateful/main.go b/_examples/stateful/main.go index f61c541c..3593dd30 100644 --- a/_examples/stateful/main.go +++ b/_examples/stateful/main.go @@ -49,15 +49,14 @@ var ( {"ExprEnd", `}`, lexer.Pop()}, }, }) - parser = participle.MustBuild(&String{}, participle.Lexer(def), + parser = participle.MustBuild[String](participle.Lexer(def), participle.Elide("Whitespace")) ) func main() { - actual := &String{} - err := parser.ParseString("", `"hello $(world) ${first + "${last}"}"`, actual) + actual, err := parser.ParseString("", `"hello $(world) ${first + "${last}"}"`) + repr.Println(actual) if err != nil { log.Fatal(err) } - repr.Println(actual) } diff --git a/_examples/stateful/main_test.go b/_examples/stateful/main_test.go index 7df75c53..0165e8f7 100644 --- a/_examples/stateful/main_test.go +++ b/_examples/stateful/main_test.go @@ -8,8 +8,7 @@ import ( ) func TestExe(t *testing.T) { - actual := &String{} - err := parser.ParseString("", `"hello $(world) ${first + "${last}"}"`, actual) + actual, err := parser.ParseString("", `"hello $(world) ${first + "${last}"}"`) if err != nil { log.Fatal(err) } diff --git a/_examples/thrift/main.go b/_examples/thrift/main.go index cb6e77bc..d71ba6b8 100644 --- a/_examples/thrift/main.go +++ b/_examples/thrift/main.go @@ -200,7 +200,7 @@ var ( {"Punct", `[,.<>(){}=:]`}, {"Comment", `//.*`}, }) - parser = participle.MustBuild(&Thrift{}, + parser = participle.MustBuild[Thrift]( participle.Lexer(def), participle.Unquote(), participle.Elide("Whitespace"), @@ -225,10 +225,9 @@ func main() { } for _, file := range cli.Files { - thrift := &Thrift{} r, err := os.Open(file) ctx.FatalIfErrorf(err, "") - err = parser.Parse("", r, thrift) + thrift, err := parser.Parse("", r) ctx.FatalIfErrorf(err, "") repr.Println(thrift) } diff --git a/_examples/thrift/main_test.go b/_examples/thrift/main_test.go index 69ce4eeb..82617fc3 100644 --- a/_examples/thrift/main_test.go +++ b/_examples/thrift/main_test.go @@ -58,8 +58,7 @@ service Twitter { ) func BenchmarkParticipleThrift(b *testing.B) { - thrift := &Thrift{} - err := parser.ParseString("", source, thrift) + _, err := parser.ParseString("", source) require.NoError(b, err) b.ResetTimer() @@ -67,21 +66,19 @@ func BenchmarkParticipleThrift(b *testing.B) { start := time.Now() for i := 0; i < b.N; i++ { - thrift := &Thrift{} - _ = parser.ParseString("", source, thrift) + _, _ = parser.ParseString("", source) } b.ReportMetric(float64(len(source)*b.N)*float64(time.Since(start)/time.Second)/1024/1024, "MiB/s") } func BenchmarkParticipleThriftGenerated(b *testing.B) { - parser := participle.MustBuild(&Thrift{}, + parser := participle.MustBuild[Thrift]( participle.Lexer(Lexer), participle.Unquote(), participle.Elide("Whitespace"), ) - thrift := &Thrift{} - err := parser.ParseString("", source, thrift) + _, err := parser.ParseString("", source) require.NoError(b, err) b.ResetTimer() @@ -89,8 +86,7 @@ func BenchmarkParticipleThriftGenerated(b *testing.B) { start := time.Now() for i := 0; i < b.N; i++ { - thrift := &Thrift{} - _ = parser.ParseString("", source, thrift) + _, _ = parser.ParseString("", source) } b.ReportMetric(float64(len(source)*b.N)*float64(time.Since(start)/time.Second)/1024/1024, "MiB/s") } diff --git a/_examples/toml/main.go b/_examples/toml/main.go index 00573c7f..3e86e19f 100644 --- a/_examples/toml/main.go +++ b/_examples/toml/main.go @@ -54,10 +54,8 @@ var ( {"comment", `#[^\n]+`}, {"whitespace", `\s+`}, }) - tomlParser = participle.MustBuild(&TOML{}, - participle.Lexer( - tomlLexer, - ), + tomlParser = participle.MustBuild[TOML]( + participle.Lexer(tomlLexer), participle.Unquote("String"), ) @@ -68,11 +66,10 @@ var ( func main() { ctx := kong.Parse(&cli) - toml := &TOML{} r, err := os.Open(cli.File) ctx.FatalIfErrorf(err) defer r.Close() - err = tomlParser.Parse(cli.File, r, toml) + toml, err := tomlParser.Parse(cli.File, r) ctx.FatalIfErrorf(err) repr.Println(toml) } diff --git a/_examples/toml/main_test.go b/_examples/toml/main_test.go index 071429d8..cab7d812 100644 --- a/_examples/toml/main_test.go +++ b/_examples/toml/main_test.go @@ -8,8 +8,7 @@ import ( ) func TestExe(t *testing.T) { - toml := &TOML{} - err := tomlParser.ParseString("", ` + toml, err := tomlParser.ParseString("", ` # This is a TOML document. title = "TOML Example" @@ -44,7 +43,7 @@ hosts = [ "alpha", "omega" ] -`, toml) +`) require.NoError(t, err) repr.Println(toml) } diff --git a/ebnf.go b/ebnf.go index b3b0c027..34c894df 100644 --- a/ebnf.go +++ b/ebnf.go @@ -8,7 +8,7 @@ import ( // String returns the EBNF for the grammar. // // Productions are always upper cased. Lexer tokens are always lower case. -func (p *Parser) String() string { +func (p *Parser[G]) String() string { return ebnf(p.typeNodes[p.rootType]) } diff --git a/ebnf/ebnf.go b/ebnf/ebnf.go index f79de0b7..30088a44 100644 --- a/ebnf/ebnf.go +++ b/ebnf/ebnf.go @@ -17,7 +17,7 @@ import ( "github.com/alecthomas/participle/v2" ) -var parser = participle.MustBuild(&EBNF{}) +var parser = participle.MustBuild[EBNF]() // A Node in the EBNF grammar. type Node interface { @@ -167,12 +167,10 @@ func (e *EBNF) String() (out string) { // ParseString string into EBNF. func ParseString(ebnf string) (*EBNF, error) { - out := &EBNF{} - return out, parser.ParseString("", ebnf, out) + return parser.ParseString("", ebnf) } // Parse io.Reader into EBNF. func Parse(r io.Reader) (*EBNF, error) { - out := &EBNF{} - return out, parser.Parse("", r, out) + return parser.Parse("", r) } diff --git a/ebnf_test.go b/ebnf_test.go index 7fd737cf..045c01e3 100644 --- a/ebnf_test.go +++ b/ebnf_test.go @@ -9,7 +9,7 @@ import ( ) func TestEBNF(t *testing.T) { - parser := mustTestParser(t, &EBNF{}) + parser := mustTestParser[EBNF](t) expected := ` EBNF = Production* . Production = "=" Expression+ "." . @@ -34,7 +34,7 @@ func TestEBNF_Other(t *testing.T) { Negation string `| !("anything" | 'but')` } - parser := mustTestParser(t, &Grammar{}) + parser := mustTestParser[Grammar](t) expected := `Grammar = ((?= "good") ) | ((?! "bad" | "worse") ) | ~("anything" | "but") .` require.Equal(t, expected, parser.String()) } @@ -64,7 +64,7 @@ func TestEBNF_Union(t *testing.T) { TheUnion EBNFUnion `@@` } - parser := mustTestParser(t, &Grammar{}, participle.Union[EBNFUnion](EBNFUnionA{}, EBNFUnionB{}, EBNFUnionC{})) + parser := mustTestParser[Grammar](t, participle.Union[EBNFUnion](EBNFUnionA{}, EBNFUnionB{}, EBNFUnionC{})) require.Equal(t, strings.TrimSpace(` Grammar = EBNFUnion . diff --git a/error_test.go b/error_test.go index c5b0f65a..fcdafeb7 100644 --- a/error_test.go +++ b/error_test.go @@ -25,20 +25,18 @@ func TestErrorReporting(t *testing.T) { type grammar struct { Decls []*decl `( @@ ";" )*` } - p := mustTestParser(t, &grammar{}, participle.UseLookahead(5)) + p := mustTestParser[grammar](t, participle.UseLookahead(5)) - var err error - ast := &grammar{} - err = p.ParseString("", `public class A;`, ast) + ast, err := p.ParseString("", `public class A;`) require.NoError(t, err) require.Equal(t, &grammar{Decls: []*decl{ {Class: &cls{Visibility: "public", Class: "A"}}, }}, ast) - err = p.ParseString("", `public union A;`, ast) + _, err = p.ParseString("", `public union A;`) require.NoError(t, err) - err = p.ParseString("", `public struct Bar;`, ast) + _, err = p.ParseString("", `public struct Bar;`) require.EqualError(t, err, `1:8: unexpected token "struct" (expected "union" )`) - err = p.ParseString("", `public class 1;`, ast) + _, err = p.ParseString("", `public class 1;`) require.EqualError(t, err, `1:14: unexpected token "1" (expected )`) } diff --git a/grammar_test.go b/grammar_test.go index bc09b52f..f62c8803 100644 --- a/grammar_test.go +++ b/grammar_test.go @@ -11,7 +11,7 @@ func TestBuild_Errors_Negation(t *testing.T) { type grammar struct { Whatever string `'a' | ! | 'b'` } - _, err := participle.Build(&grammar{}) + _, err := participle.Build[grammar]() require.EqualError(t, err, "Whatever: unexpected token |") } @@ -19,7 +19,7 @@ func TestBuild_Errors_Capture(t *testing.T) { type grammar struct { Whatever string `'a' | @ | 'b'` } - _, err := participle.Build(&grammar{}) + _, err := participle.Build[grammar]() require.EqualError(t, err, "Whatever: unexpected token |") } @@ -27,7 +27,7 @@ func TestBuild_Errors_UnclosedGroup(t *testing.T) { type grammar struct { Whatever string `'a' | ('b' | 'c'` } - _, err := participle.Build(&grammar{}) + _, err := participle.Build[grammar]() require.EqualError(t, err, `Whatever: expected ) but got ""`) } @@ -35,7 +35,7 @@ func TestBuild_Errors_LookaheadGroup(t *testing.T) { type grammar struct { Whatever string `'a' | (?? 'what') | 'b'` } - _, err := participle.Build(&grammar{}) + _, err := participle.Build[grammar]() require.EqualError(t, err, `Whatever: expected = or ! but got "?"`) } @@ -46,7 +46,7 @@ func TestBuild_Colon_OK(t *testing.T) { SinglePresent bool `| 'SinglePresent' ':' Ident` SingleCapture string `| 'SingleCapture' ':' @Ident` } - parser, err := participle.Build(&grammar{}) + parser, err := participle.Build[grammar]() require.NoError(t, err) require.Equal(t, `Grammar = "TokenTypeTest"`+ ` | ("DoubleCapture" ":" )`+ @@ -58,6 +58,6 @@ func TestBuild_Colon_MissingTokenType(t *testing.T) { type grammar struct { Key string `'name' : @Ident` } - _, err := participle.Build(&grammar{}) + _, err := participle.Build[grammar]() require.EqualError(t, err, `Key: expected identifier for literal type constraint but got "@"`) } diff --git a/lexer/stateful_test.go b/lexer/stateful_test.go index bec8ba73..be7b5e44 100644 --- a/lexer/stateful_test.go +++ b/lexer/stateful_test.go @@ -230,13 +230,12 @@ func ExampleNew() { if err != nil { log.Fatal(err) } - parser, err := participle.Build(&String{}, participle.Lexer(def)) + parser, err := participle.Build[String](participle.Lexer(def)) if err != nil { log.Fatal(err) } - actual := &String{} - err = parser.ParseString("", `"hello ${user + "??"}"`, actual) + actual, err := parser.ParseString("", `"hello ${user + "??"}"`) if err != nil { log.Fatal(err) } @@ -284,11 +283,10 @@ func TestStateful(t *testing.T) { }, }) require.NoError(t, err) - parser, err := participle.Build(&String{}, participle.Lexer(def)) + parser, err := participle.Build[String](participle.Lexer(def)) require.NoError(t, err) - actual := &String{} - err = parser.ParseString("", `"hello ${user + "${last}"}"`, actual) + actual, err := parser.ParseString("", `"hello ${user + "${last}"}"`) require.NoError(t, err) expected := &String{ Fragments: []*Fragment{ @@ -335,9 +333,7 @@ func TestHereDoc(t *testing.T) { }, }) require.NoError(t, err) - parser, err := participle.Build(&AST{}, - participle.Lexer(def), - ) + parser, err := participle.Build[AST](participle.Lexer(def)) require.NoError(t, err) expected := &AST{ @@ -345,12 +341,11 @@ func TestHereDoc(t *testing.T) { Idents: []string{"hello", "world"}, }, } - actual := &AST{} - err = parser.ParseString("", ` + actual, err := parser.ParseString("", ` <" "=" | ">" | "<" "=" | "<" )` Next *grammar ` @@ ]` } - p := mustTestParser(t, &grammar{}, participle.UseLookahead(5)) - actual := &grammar{} - err := p.ParseString("", "a >= b", actual) + p := mustTestParser[grammar](t, participle.UseLookahead(5)) + _, err := p.ParseString("", "a >= b") require.NoError(t, err) } @@ -289,14 +281,12 @@ func TestIssue27(t *testing.T) { Number int ` @(["-"] Int)` String string `| @String` } - p := mustTestParser(t, &grammar{}) - actual := &grammar{} - - err := p.ParseString("", `- 100`, actual) + p := mustTestParser[grammar](t) + actual, err := p.ParseString("", `- 100`) require.NoError(t, err) require.Equal(t, &grammar{Number: -100}, actual) - err = p.ParseString("", `100`, actual) + actual, err = p.ParseString("", `100`) require.NoError(t, err) require.Equal(t, &grammar{Number: 100}, actual) } @@ -307,14 +297,13 @@ func TestLookaheadDisambiguateByType(t *testing.T) { Float float64 `| @(["-"] Float)` } - p := mustTestParser(t, &grammar{}, participle.UseLookahead(5)) - actual := &grammar{} + p := mustTestParser[grammar](t, participle.UseLookahead(5)) - err := p.ParseString("", `- 100`, actual) + actual, err := p.ParseString("", `- 100`) require.NoError(t, err) require.Equal(t, &grammar{Int: -100}, actual) - err = p.ParseString("", `- 100.5`, actual) + actual, err = p.ParseString("", `- 100.5`) require.NoError(t, err) require.Equal(t, &grammar{Float: -100.5}, actual) } @@ -324,9 +313,8 @@ func TestShowNearestError(t *testing.T) { A string ` @"a" @"b" @"c"` B string `| @"a" @"z"` } - p := mustTestParser(t, &grammar{}, participle.UseLookahead(10)) - actual := &grammar{} - err := p.ParseString("", `a b d`, actual) + p := mustTestParser[grammar](t, participle.UseLookahead(10)) + _, err := p.ParseString("", `a b d`) require.EqualError(t, err, `1:5: unexpected token "d" (expected "c")`) } @@ -335,9 +323,8 @@ func TestRewindDisjunction(t *testing.T) { Function string ` @Ident "(" ")"` Ident string `| @Ident` } - p := mustTestParser(t, &grammar{}, participle.UseLookahead(2)) - ast := &grammar{} - err := p.ParseString("", `name`, ast) + p := mustTestParser[grammar](t, participle.UseLookahead(2)) + ast, err := p.ParseString("", `name`) require.NoError(t, err) require.Equal(t, &grammar{Ident: "name"}, ast) } @@ -346,14 +333,13 @@ func TestRewindOptional(t *testing.T) { type grammar struct { Var string ` [ "int" "int" ] @Ident` } - p := mustTestParser(t, &grammar{}, participle.UseLookahead(3)) - ast := &grammar{} + p := mustTestParser[grammar](t, participle.UseLookahead(3)) - err := p.ParseString("", `one`, ast) + ast, err := p.ParseString("", `one`) require.NoError(t, err) require.Equal(t, &grammar{Var: "one"}, ast) - err = p.ParseString("", `int int one`, ast) + ast, err = p.ParseString("", `int int one`) require.NoError(t, err) require.Equal(t, &grammar{Var: "one"}, ast) } @@ -363,14 +349,13 @@ func TestRewindRepetition(t *testing.T) { Ints []string `(@"int")*` Ident string `@Ident` } - p := mustTestParser(t, &grammar{}, participle.UseLookahead(3)) - ast := &grammar{} + p := mustTestParser[grammar](t, participle.UseLookahead(3)) - err := p.ParseString("", `int int one`, ast) + ast, err := p.ParseString("", `int int one`) require.NoError(t, err) require.Equal(t, &grammar{Ints: []string{"int", "int"}, Ident: "one"}, ast) - err = p.ParseString("", `int int one`, ast) + ast, err = p.ParseString("", `int int one`) require.NoError(t, err) require.Equal(t, &grammar{Ints: []string{"int", "int"}, Ident: "one"}, ast) } diff --git a/map.go b/map.go index 24f03c89..c80290c9 100644 --- a/map.go +++ b/map.go @@ -22,7 +22,7 @@ type Mapper func(token lexer.Token) (lexer.Token, error) // // "symbols" specifies the token symbols that the Mapper will be applied to. If empty, all tokens will be mapped. func Map(mapper Mapper, symbols ...string) Option { - return func(p *Parser) error { + return func(p *parserOptions) error { p.mappers = append(p.mappers, mapperByToken{ mapper: mapper, symbols: symbols, @@ -73,7 +73,7 @@ func Upper(types ...string) Option { // Elide drops tokens of the specified types. func Elide(types ...string) Option { - return func(p *Parser) error { + return func(p *parserOptions) error { p.elide = append(p.elide, types...) return nil } diff --git a/map_test.go b/map_test.go index 5514924d..90ce1402 100644 --- a/map_test.go +++ b/map_test.go @@ -10,14 +10,14 @@ import ( ) func TestUpper(t *testing.T) { - var grammar struct { + type grammar struct { Text string `@Ident` } def := lexer.MustSimple([]lexer.SimpleRule{ {"Whitespace", `\s+`}, {"Ident", `\w+`}, }) - parser := mustTestParser(t, &grammar, participle.Lexer(def), participle.Upper("Ident")) + parser := mustTestParser[grammar](t, participle.Lexer(def), participle.Upper("Ident")) actual, err := parser.Lex("", strings.NewReader("hello world")) require.NoError(t, err) @@ -32,7 +32,7 @@ func TestUpper(t *testing.T) { } func TestUnquote(t *testing.T) { - var grammar struct { + type grammar struct { Text string `@Ident` } lex := lexer.MustSimple([]lexer.SimpleRule{ @@ -41,7 +41,7 @@ func TestUnquote(t *testing.T) { {"String", `\"(?:[^\"]|\\.)*\"`}, {"RawString", "`[^`]*`"}, }) - parser := mustTestParser(t, &grammar, participle.Lexer(lex), participle.Unquote("String", "RawString")) + parser := mustTestParser[grammar](t, participle.Lexer(lex), participle.Unquote("String", "RawString")) actual, err := parser.Lex("", strings.NewReader("hello world \"quoted\\tstring\" `backtick quotes`")) require.NoError(t, err) expected := []lexer.Token{ diff --git a/options.go b/options.go index 3c8dae7d..65482238 100644 --- a/options.go +++ b/options.go @@ -14,11 +14,11 @@ import ( const MaxLookahead = 99999 // An Option to modify the behaviour of the Parser. -type Option func(p *Parser) error +type Option func(p *parserOptions) error // Lexer is an Option that sets the lexer to use with the given grammar. func Lexer(def lexer.Definition) Option { - return func(p *Parser) error { + return func(p *parserOptions) error { p.lex = def return nil } @@ -39,7 +39,7 @@ func Lexer(def lexer.Definition) Option { // Using infinite lookahead can be useful for testing, or for parsing especially // ambiguous grammars. Use at your own risk! func UseLookahead(n int) Option { - return func(p *Parser) error { + return func(p *parserOptions) error { p.useLookahead = n return nil } @@ -50,7 +50,7 @@ func UseLookahead(n int) Option { // Note that the lexer itself will also have to be case-insensitive; this option // just controls whether literals in the grammar are matched case insensitively. func CaseInsensitive(tokens ...string) Option { - return func(p *Parser) error { + return func(p *parserOptions) error { for _, token := range tokens { p.caseInsensitive[token] = true } @@ -70,7 +70,7 @@ func CaseInsensitive(tokens ...string) Option { // This can be useful if you want to parse a DSL within the larger grammar, or if you want // to implement an optimized parsing scheme for some portion of the grammar. func ParseTypeWith[T any](parseFn func(*lexer.PeekingLexer) (T, error)) Option { - return func(p *Parser) error { + return func(p *parserOptions) error { parseFnVal := reflect.ValueOf(parseFn) parseFnType := parseFnVal.Type() if parseFnType.Out(0).Kind() != reflect.Interface { @@ -95,7 +95,7 @@ func ParseTypeWith[T any](parseFn func(*lexer.PeekingLexer) (T, error)) Option { // and the source string is "AB", then the parser will only match A, and will not // try to parse the second member at all. func Union[T any](members ...T) Option { - return func(p *Parser) error { + return func(p *parserOptions) error { unionType := reflect.TypeOf((*T)(nil)).Elem() if unionType.Kind() != reflect.Interface { return fmt.Errorf("Union: union type must be an interface (got %s)", unionType) diff --git a/parser.go b/parser.go index ada4496e..d76d4fae 100644 --- a/parser.go +++ b/parser.go @@ -20,8 +20,7 @@ type customDef struct { parseFn reflect.Value } -// A Parser for a particular grammar and lexer. -type Parser struct { +type parserOptions struct { lex lexer.Definition rootType reflect.Type typeNodes map[reflect.Type]node @@ -33,9 +32,24 @@ type Parser struct { elide []string } -// MustBuild calls Build(grammar, options...) and panics if an error occurs. -func MustBuild(grammar interface{}, options ...Option) *Parser { - parser, err := Build(grammar, options...) +// A Parser for a particular grammar and lexer. +type Parser[G any] struct { + parserOptions +} + +// ParserForProduction returns a new parser for the given production in grammar G. +func ParserForProduction[P, G any](parser *Parser[G]) (*Parser[P], error) { + t := reflect.TypeOf(*new(P)) + _, ok := parser.typeNodes[t] + if !ok { + return nil, fmt.Errorf("parser does not contain a production of type %s", t) + } + return (*Parser[P])(parser), nil +} + +// MustBuild calls Build[G](options...) and panics if an error occurs. +func MustBuild[G any](options ...Option) *Parser[G] { + parser, err := Build[G](options...) if err != nil { panic(err) } @@ -48,15 +62,17 @@ func MustBuild(grammar interface{}, options ...Option) *Parser { // like tokens. // // See documentation for details. -func Build(grammar interface{}, options ...Option) (parser *Parser, err error) { - // Configure Parser struct with defaults + options. - p := &Parser{ - lex: lexer.TextScannerLexer, - caseInsensitive: map[string]bool{}, - useLookahead: 1, +func Build[G any](options ...Option) (parser *Parser[G], err error) { + // Configure Parser[G] struct with defaults + options. + p := &Parser[G]{ + parserOptions: parserOptions{ + lex: lexer.TextScannerLexer, + caseInsensitive: map[string]bool{}, + useLookahead: 1, + }, } for _, option := range options { - if err = option(p); err != nil { + if err = option(&p.parserOptions); err != nil { return nil, err } } @@ -101,7 +117,8 @@ func Build(grammar interface{}, options ...Option) (parser *Parser, err error) { return nil, err } - v := reflect.ValueOf(grammar) + var grammar G + v := reflect.ValueOf(&grammar) if v.Kind() == reflect.Interface { v = v.Elem() } @@ -119,13 +136,13 @@ func Build(grammar interface{}, options ...Option) (parser *Parser, err error) { } // Lexer returns the parser's builtin lexer. -func (p *Parser) Lexer() lexer.Definition { +func (p *Parser[G]) Lexer() lexer.Definition { return p.lex } // Lex uses the parser's lexer to tokenise input. // Parameter filename is used as an opaque prefix in error messages. -func (p *Parser) Lex(filename string, r io.Reader) ([]lexer.Token, error) { +func (p *Parser[G]) Lex(filename string, r io.Reader) ([]lexer.Token, error) { lex, err := p.lex.Lex(filename, r) if err != nil { return nil, err @@ -138,11 +155,12 @@ func (p *Parser) Lex(filename string, r io.Reader) ([]lexer.Token, error) { // Build(). // // This may return a Error. -func (p *Parser) ParseFromLexer(lex *lexer.PeekingLexer, v interface{}, options ...ParseOption) error { +func (p *Parser[G]) ParseFromLexer(lex *lexer.PeekingLexer, options ...ParseOption) (*G, error) { + v := new(G) rv := reflect.ValueOf(v) parseNode, err := p.parseNodeFor(rv) if err != nil { - return err + return nil, err } caseInsensitive := map[lexer.TokenType]bool{} for sym, tt := range p.lex.Symbols() { @@ -156,40 +174,40 @@ func (p *Parser) ParseFromLexer(lex *lexer.PeekingLexer, v interface{}, options option(ctx) } // If the grammar implements Parseable, use it. - if parseable, ok := v.(Parseable); ok { - return p.rootParseable(ctx, parseable) + if parseable, ok := any(v).(Parseable); ok { + return v, p.rootParseable(ctx, parseable) } - return p.parseOne(ctx, parseNode, rv) + return v, p.parseOne(ctx, parseNode, rv) } -func (p *Parser) parse(lex lexer.Lexer, v interface{}, options ...ParseOption) (err error) { +func (p *Parser[G]) parse(lex lexer.Lexer, options ...ParseOption) (v *G, err error) { peeker, err := lexer.Upgrade(lex, p.getElidedTypes()...) if err != nil { - return err + return nil, err } - return p.ParseFromLexer(peeker, v, options...) + return p.ParseFromLexer(peeker, options...) } // Parse from r into grammar v which must be of the same type as the grammar passed to // Build(). Parameter filename is used as an opaque prefix in error messages. // // This may return an Error. -func (p *Parser) Parse(filename string, r io.Reader, v interface{}, options ...ParseOption) (err error) { +func (p *Parser[G]) Parse(filename string, r io.Reader, options ...ParseOption) (v *G, err error) { if filename == "" { filename = lexer.NameOfReader(r) } lex, err := p.lex.Lex(filename, r) if err != nil { - return err + return nil, err } - return p.parse(lex, v, options...) + return p.parse(lex, options...) } // ParseString from s into grammar v which must be of the same type as the grammar passed to // Build(). Parameter filename is used as an opaque prefix in error messages. // // This may return an Error. -func (p *Parser) ParseString(filename string, s string, v interface{}, options ...ParseOption) (err error) { +func (p *Parser[G]) ParseString(filename string, s string, options ...ParseOption) (v *G, err error) { var lex lexer.Lexer if sl, ok := p.lex.(lexer.StringDefinition); ok { lex, err = sl.LexString(filename, s) @@ -197,16 +215,16 @@ func (p *Parser) ParseString(filename string, s string, v interface{}, options . lex, err = p.lex.Lex(filename, strings.NewReader(s)) } if err != nil { - return err + return nil, err } - return p.parse(lex, v, options...) + return p.parse(lex, options...) } // ParseBytes from b into grammar v which must be of the same type as the grammar passed to // Build(). Parameter filename is used as an opaque prefix in error messages. // // This may return an Error. -func (p *Parser) ParseBytes(filename string, b []byte, v interface{}, options ...ParseOption) (err error) { +func (p *Parser[G]) ParseBytes(filename string, b []byte, options ...ParseOption) (v *G, err error) { var lex lexer.Lexer if sl, ok := p.lex.(lexer.BytesDefinition); ok { lex, err = sl.LexBytes(filename, b) @@ -214,12 +232,12 @@ func (p *Parser) ParseBytes(filename string, b []byte, v interface{}, options .. lex, err = p.lex.Lex(filename, bytes.NewReader(b)) } if err != nil { - return err + return nil, err } - return p.parse(lex, v, options...) + return p.parse(lex, options...) } -func (p *Parser) parseOne(ctx *parseContext, parseNode node, rv reflect.Value) error { +func (p *Parser[G]) parseOne(ctx *parseContext, parseNode node, rv reflect.Value) error { err := p.parseInto(ctx, parseNode, rv) if err != nil { return err @@ -231,7 +249,7 @@ func (p *Parser) parseOne(ctx *parseContext, parseNode node, rv reflect.Value) e return nil } -func (p *Parser) parseInto(ctx *parseContext, parseNode node, rv reflect.Value) error { +func (p *Parser[G]) 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 or interface, but is a nil %s", rv.Type()) } @@ -249,7 +267,7 @@ func (p *Parser) parseInto(ctx *parseContext, parseNode node, rv reflect.Value) return nil } -func (p *Parser) rootParseable(ctx *parseContext, parseable Parseable) error { +func (p *Parser[G]) rootParseable(ctx *parseContext, parseable Parseable) error { if err := parseable.Parse(ctx.PeekingLexer); err != nil { if err == NextMatch { err = UnexpectedTokenError{Unexpected: ctx.Peek()} @@ -265,7 +283,7 @@ func (p *Parser) rootParseable(ctx *parseContext, parseable Parseable) error { return nil } -func (p *Parser) getElidedTypes() []lexer.TokenType { +func (p *Parser[G]) getElidedTypes() []lexer.TokenType { symbols := p.lex.Symbols() elideTypes := make([]lexer.TokenType, 0, len(p.elide)) for _, elide := range p.elide { @@ -278,7 +296,7 @@ func (p *Parser) getElidedTypes() []lexer.TokenType { return elideTypes } -func (p *Parser) parseNodeFor(v reflect.Value) (node, error) { +func (p *Parser[G]) parseNodeFor(v reflect.Value) (node, error) { t := v.Type() if t.Kind() == reflect.Interface { t = t.Elem() diff --git a/parser_test.go b/parser_test.go index 8aba3358..3ad8d334 100644 --- a/parser_test.go +++ b/parser_test.go @@ -21,7 +21,7 @@ func TestProductionCapture(t *testing.T) { A string `@Test` } - _, err := participle.Build(&testCapture{}) + _, err := participle.Build[testCapture]() require.Error(t, err) } @@ -30,12 +30,11 @@ func TestTermCapture(t *testing.T) { A string `@"."*` } - parser := mustTestParser(t, &grammar{}) + parser := mustTestParser[grammar](t) - actual := &grammar{} expected := &grammar{"..."} - err := parser.ParseString("", "...", actual) + actual, err := parser.ParseString("", "...") require.NoError(t, err) require.Equal(t, expected, actual) } @@ -45,10 +44,9 @@ func TestParseScalar(t *testing.T) { A string `@"one"` } - parser := mustTestParser(t, &grammar{}) + parser := mustTestParser[grammar](t) - actual := &grammar{} - err := parser.ParseString("", "one", actual) + actual, err := parser.ParseString("", "one") require.NoError(t, err) require.Equal(t, &grammar{"one"}, actual) } @@ -58,15 +56,13 @@ func TestParseGroup(t *testing.T) { A string `@("one" | "two")` } - parser := mustTestParser(t, &grammar{}) + parser := mustTestParser[grammar](t) - actual := &grammar{} - err := parser.ParseString("", "one", actual) + actual, err := parser.ParseString("", "one") require.NoError(t, err) require.Equal(t, &grammar{"one"}, actual) - actual = &grammar{} - err = parser.ParseString("", "two", actual) + actual, err = parser.ParseString("", "two") require.NoError(t, err) require.Equal(t, &grammar{"two"}, actual) } @@ -77,15 +73,13 @@ func TestParseAlternative(t *testing.T) { B string `@"two"` } - parser := mustTestParser(t, &grammar{}) + parser := mustTestParser[grammar](t) - actual := &grammar{} - err := parser.ParseString("", "one", actual) + actual, err := parser.ParseString("", "one") require.NoError(t, err) require.Equal(t, &grammar{A: "one"}, actual) - actual = &grammar{} - err = parser.ParseString("", "two", actual) + actual, err = parser.ParseString("", "two") require.NoError(t, err) require.Equal(t, &grammar{B: "two"}, actual) } @@ -97,17 +91,15 @@ func TestParseSequence(t *testing.T) { C string `@"three"` } - parser := mustTestParser(t, &grammar{}) + parser := mustTestParser[grammar](t) - actual := &grammar{} expected := &grammar{"one", "two", "three"} - err := parser.ParseString("", "one two three", actual) + actual, err := parser.ParseString("", "one two three") require.NoError(t, err) require.Equal(t, expected, actual) - actual = &grammar{} expected = &grammar{} - err = parser.ParseString("", "moo", actual) + actual, err = parser.ParseString("", "moo") require.Error(t, err) require.Equal(t, expected, actual) } @@ -122,11 +114,10 @@ func TestNested(t *testing.T) { A *nestedInner `@@` } - parser := mustTestParser(t, &testNested{}) + parser := mustTestParser[testNested](t) - actual := &testNested{} expected := &testNested{A: &nestedInner{B: "one", C: "two"}} - err := parser.ParseString("", "one two", actual) + actual, err := parser.ParseString("", "one two") require.NoError(t, err) require.Equal(t, expected, actual) } @@ -140,11 +131,10 @@ func TestAccumulateNested(t *testing.T) { A []*nestedInner `@@+` } - parser := mustTestParser(t, &testAccumulateNested{}) + parser := mustTestParser[testAccumulateNested](t) - actual := &testAccumulateNested{} expected := &testAccumulateNested{A: []*nestedInner{{B: "one", C: "two"}, {B: "one", C: "two"}}} - err := parser.ParseString("", "one two one two", actual) + actual, err := parser.ParseString("", "one two one two") require.NoError(t, err) require.Equal(t, expected, actual) } @@ -153,11 +143,10 @@ func TestRepetitionNoMatch(t *testing.T) { type grammar struct { A []string `@"."*` } - parser := mustTestParser(t, &grammar{}) + parser := mustTestParser[grammar](t) expected := &grammar{} - actual := &grammar{} - err := parser.ParseString("", ``, actual) + actual, err := parser.ParseString("", ``) require.NoError(t, err) require.Equal(t, expected, actual) } @@ -166,11 +155,10 @@ func TestRepetition(t *testing.T) { type grammar struct { A []string `@"."*` } - parser := mustTestParser(t, &grammar{}) + parser := mustTestParser[grammar](t) expected := &grammar{A: []string{".", ".", "."}} - actual := &grammar{} - err := parser.ParseString("", `...`, actual) + actual, err := parser.ParseString("", `...`) require.NoError(t, err) require.Equal(t, expected, actual) } @@ -182,34 +170,31 @@ func TestRepetitionAcrossFields(t *testing.T) { C *string ` @"c")` } - parser := mustTestParser(t, &testRepetition{}) + parser := mustTestParser[testRepetition](t) b := "b" c := "c" - actual := &testRepetition{} expected := &testRepetition{ A: []string{".", ".", "."}, B: &b, } - err := parser.ParseString("", "...b", actual) + actual, err := parser.ParseString("", "...b") require.NoError(t, err) require.Equal(t, expected, actual) - actual = &testRepetition{} expected = &testRepetition{ A: []string{".", ".", "."}, C: &c, } - err = parser.ParseString("", "...c", actual) + actual, err = parser.ParseString("", "...c") require.NoError(t, err) require.Equal(t, expected, actual) - actual = &testRepetition{} expected = &testRepetition{ B: &b, } - err = parser.ParseString("", "b", actual) + actual, err = parser.ParseString("", "b") require.NoError(t, err) require.Equal(t, expected, actual) } @@ -219,13 +204,12 @@ func TestAccumulateString(t *testing.T) { A string `@"."+` } - parser := mustTestParser(t, &testAccumulateString{}) + parser := mustTestParser[testAccumulateString](t) - actual := &testAccumulateString{} expected := &testAccumulateString{ A: "...", } - err := parser.ParseString("", "...", actual) + actual, err := parser.ParseString("", "...") require.NoError(t, err) require.Equal(t, expected, actual) } @@ -288,7 +272,7 @@ type EBNF struct { } func TestEBNFParser(t *testing.T) { - parser := mustTestParser(t, &EBNF{}, participle.Unquote()) + parser := mustTestParser[EBNF](t, participle.Unquote()) expected := &EBNF{ Productions: []*Production{ @@ -459,8 +443,7 @@ func TestEBNFParser(t *testing.T) { }, }, } - actual := &EBNF{} - err := parser.ParseString("", strings.TrimSpace(` + actual, err := parser.ParseString("", strings.TrimSpace(` Production = name "=" [ Expression ] "." . Expression = Alternative { "|" Alternative } . Alternative = Term { Term } . @@ -468,7 +451,7 @@ Term = name | token [ "…" token ] | "@@" | Group | EBNFOption | Repetit Group = "(" Expression ")" . EBNFOption = "[" Expression "]" . Repetition = "{" Expression "}" . -`), actual) +`)) require.NoError(t, err) require.Equal(t, expected, actual) } @@ -485,15 +468,14 @@ func TestParseExpression(t *testing.T) { B *testNestB `@@` } - parser := mustTestParser(t, &testExpression{}) + parser := mustTestParser[testExpression](t) expected := &testExpression{ B: &testNestB{ B: "b", }, } - actual := &testExpression{} - err := parser.ParseString("", ";b", actual) + actual, err := parser.ParseString("", ";b") require.NoError(t, err) require.Equal(t, expected, actual) } @@ -504,11 +486,10 @@ func TestParseOptional(t *testing.T) { B string `@"c"` } - parser := mustTestParser(t, &testOptional{}) + parser := mustTestParser[testOptional](t) expected := &testOptional{B: "c"} - actual := &testOptional{} - err := parser.ParseString("", `c`, actual) + actual, err := parser.ParseString("", `c`) require.NoError(t, err) require.Equal(t, expected, actual) } @@ -519,24 +500,23 @@ func TestHello(t *testing.T) { To string `@String` } - parser := mustTestParser(t, &testHello{}, participle.Unquote()) + parser := mustTestParser[testHello](t, participle.Unquote()) expected := &testHello{"hello", `Bobby Brown`} - actual := &testHello{} - err := parser.ParseString("", `hello "Bobby Brown"`, actual) + actual, err := parser.ParseString("", `hello "Bobby Brown"`) require.NoError(t, err) require.Equal(t, expected, actual) } -func mustTestParser(t *testing.T, grammar interface{}, options ...participle.Option) *participle.Parser { +func mustTestParser[G any](t *testing.T, options ...participle.Option) *participle.Parser[G] { t.Helper() - parser, err := participle.Build(grammar, options...) + parser, err := participle.Build[G](options...) require.NoError(t, err) return parser } func BenchmarkEBNFParser(b *testing.B) { - parser, err := participle.Build(&EBNF{}) + parser, err := participle.Build[EBNF]() require.NoError(b, err) b.ResetTimer() source := strings.TrimSpace(` @@ -550,8 +530,7 @@ Repetition = "{" Expression "}" . `) for i := 0; i < b.N; i++ { - actual := &EBNF{} - _ = parser.ParseString("", source, actual) + _, _ = parser.ParseString("", source) } } @@ -561,12 +540,11 @@ func TestRepeatAcrossFields(t *testing.T) { B string ` @("," "<") )*` } - parser := mustTestParser(t, &grammar{}) + parser := mustTestParser[grammar](t) - actual := &grammar{} expected := &grammar{A: ".>.>.>.>", B: ",<,<,<"} - err := parser.ParseString("", ".>,<.>.>,<.>,<", actual) + actual, err := parser.ParseString("", ".>,<.>.>,<.>,<") require.NoError(t, err) require.Equal(t, expected, actual) } @@ -585,9 +563,8 @@ func TestPosInjection(t *testing.T) { EndPos lexer.Position } - parser := mustTestParser(t, &grammar{}) + parser := mustTestParser[grammar](t) - actual := &grammar{} expected := &grammar{ Pos: lexer.Position{ Offset: 3, @@ -616,7 +593,7 @@ func TestPosInjection(t *testing.T) { }, } - err := parser.ParseString("", " ...,,,.", actual) + actual, err := parser.ParseString("", " ...,,,.") require.NoError(t, err) require.Equal(t, expected, actual) } @@ -633,10 +610,9 @@ func TestCaptureInterface(t *testing.T) { Count parseableCount `@"a"*` } - parser := mustTestParser(t, &grammar{}) - actual := &grammar{} + parser := mustTestParser[grammar](t) expected := &grammar{Count: 3} - err := parser.ParseString("", "a a a", actual) + actual, err := parser.ParseString("", "a a a") require.NoError(t, err) require.Equal(t, expected, actual) } @@ -653,10 +629,9 @@ func TestTextUnmarshalerInterface(t *testing.T) { Count unmarshallableCount `{ @"a" }` } - parser := mustTestParser(t, &grammar{}) - actual := &grammar{} + parser := mustTestParser[grammar](t) expected := &grammar{Count: 3} - err := parser.ParseString("", "a a a", actual) + actual, err := parser.ParseString("", "a a a") require.NoError(t, err) require.Equal(t, expected, actual) } @@ -666,15 +641,14 @@ func TestLiteralTypeConstraint(t *testing.T) { Literal string `@"123456":String` } - parser := mustTestParser(t, &grammar{}, participle.Unquote()) + parser := mustTestParser[grammar](t, participle.Unquote()) - actual := &grammar{} expected := &grammar{Literal: "123456"} - err := parser.ParseString("", `"123456"`, actual) + actual, err := parser.ParseString("", `"123456"`) require.NoError(t, err) require.Equal(t, expected, actual) - err = parser.ParseString("", `123456`, actual) + _, err = parser.ParseString("", `123456`) require.Error(t, err) } @@ -692,12 +666,11 @@ func TestStructCaptureInterface(t *testing.T) { Capture *nestedCapture `@String` } - parser, err := participle.Build(&grammar{}, participle.Unquote()) + parser, err := participle.Build[grammar](participle.Unquote()) require.NoError(t, err) - actual := &grammar{} expected := &grammar{Capture: &nestedCapture{Tokens: []string{"hello"}}} - err = parser.ParseString("", `"hello"`, actual) + actual, err := parser.ParseString("", `"hello"`) require.NoError(t, err) require.Equal(t, expected, actual) } @@ -722,12 +695,11 @@ func TestParseable(t *testing.T) { Inner *parseableStruct `@@` } - parser, err := participle.Build(&grammar{}, participle.Unquote()) + parser, err := participle.Build[grammar](participle.Unquote()) require.NoError(t, err) - actual := &grammar{} expected := &grammar{Inner: &parseableStruct{Tokens: []string{"hello", "123", "world"}}} - err = parser.ParseString("", `hello 123 "world"`, actual) + actual, err := parser.ParseString("", `hello 123 "world"`) require.NoError(t, err) require.Equal(t, expected, actual) } @@ -737,12 +709,11 @@ func TestStringConcat(t *testing.T) { Field string `@"."+` } - parser, err := participle.Build(&grammar{}) + parser, err := participle.Build[grammar]() require.NoError(t, err) - actual := &grammar{} expected := &grammar{"...."} - err = parser.ParseString("", `. . . .`, actual) + actual, err := parser.ParseString("", `. . . .`) require.NoError(t, err) require.Equal(t, expected, actual) } @@ -752,11 +723,10 @@ func TestParseIntSlice(t *testing.T) { Field []int `@Int+` } - parser := mustTestParser(t, &grammar{}) + parser := mustTestParser[grammar](t) - actual := &grammar{} expected := &grammar{[]int{1, 2, 3, 4}} - err := parser.ParseString("", `1 2 3 4`, actual) + actual, err := parser.ParseString("", `1 2 3 4`) require.NoError(t, err) require.Equal(t, expected, actual) } @@ -765,19 +735,19 @@ func TestEmptyStructErrorsNotPanicsIssue21(t *testing.T) { type grammar struct { Foo struct{} `@@` } - _, err := participle.Build(&grammar{}) + _, err := participle.Build[grammar]() require.Error(t, err) } func TestMultipleTokensIntoScalar(t *testing.T) { - var grammar struct { + type grammar struct { Field int `@("-" Int)` } - p, err := participle.Build(&grammar) + p, err := participle.Build[grammar]() require.NoError(t, err) - err = p.ParseString("", `- 10`, &grammar) + actual, err := p.ParseString("", `- 10`) require.NoError(t, err) - require.Equal(t, -10, grammar.Field) + require.Equal(t, -10, actual.Field) } type posMixin struct { @@ -785,18 +755,18 @@ type posMixin struct { } func TestMixinPosIsPopulated(t *testing.T) { - var grammar struct { + type grammar struct { posMixin Int int `@Int` } - p := mustTestParser(t, &grammar) - err := p.ParseString("", "10", &grammar) + p := mustTestParser[grammar](t) + actual, err := p.ParseString("", "10") require.NoError(t, err) - require.Equal(t, 10, grammar.Int) - require.Equal(t, 1, grammar.Pos.Column) - require.Equal(t, 1, grammar.Pos.Line) + require.Equal(t, 10, actual.Int) + require.Equal(t, 1, actual.Pos.Column) + require.Equal(t, 1, actual.Pos.Line) } type testParserMixin struct { @@ -805,31 +775,30 @@ type testParserMixin struct { } func TestMixinFieldsAreParsed(t *testing.T) { - var grammar struct { + type grammar struct { testParserMixin C string `@Ident` } - p := mustTestParser(t, &grammar) - err := p.ParseString("", "one two three", &grammar) + p := mustTestParser[grammar](t) + actual, err := p.ParseString("", "one two three") require.NoError(t, err) - require.Equal(t, "one", grammar.A) - require.Equal(t, "two", grammar.B) - require.Equal(t, "three", grammar.C) + require.Equal(t, "one", actual.A) + require.Equal(t, "two", actual.B) + require.Equal(t, "three", actual.C) } func TestNestedOptional(t *testing.T) { type grammar struct { Args []string `"(" [ @Ident ( "," @Ident )* ] ")"` } - p := mustTestParser(t, &grammar{}) - actual := &grammar{} - err := p.ParseString("", `()`, actual) + p := mustTestParser[grammar](t) + _, err := p.ParseString("", `()`) require.NoError(t, err) - err = p.ParseString("", `(a)`, actual) + _, err = p.ParseString("", `(a)`) require.NoError(t, err) - err = p.ParseString("", `(a, b, c)`, actual) + _, err = p.ParseString("", `(a, b, c)`) require.NoError(t, err) - err = p.ParseString("", `(1)`, actual) + _, err = p.ParseString("", `(1)`) require.Error(t, err) } @@ -847,7 +816,7 @@ func TestInvalidNumbers(t *testing.T) { Float64 float64 `| "float64" @Float` } - p := mustTestParser(t, &grammar{}) + p := mustTestParser[grammar](t) tests := []struct { name string @@ -869,8 +838,7 @@ func TestInvalidNumbers(t *testing.T) { for _, test := range tests { // nolint: scopelint t.Run(test.name, func(t *testing.T) { - actual := &grammar{} - err := p.ParseString("", test.input, actual) + actual, err := p.ParseString("", test.input) if test.err { require.Error(t, err, fmt.Sprintf("%#v", actual)) } else { @@ -888,9 +856,8 @@ func TestPartialAST(t *testing.T) { Succeed string `@Ident` Fail string `@"foo"` } - p := mustTestParser(t, &grammar{}) - actual := &grammar{} - err := p.ParseString("", `foo bar`, actual) + p := mustTestParser[grammar](t) + actual, err := p.ParseString("", `foo bar`) require.Error(t, err) expected := &grammar{Succeed: "foo"} require.Equal(t, expected, actual) @@ -912,15 +879,13 @@ func TestCaseInsensitive(t *testing.T) { {"whitespace", `\s+`}, }) - p := mustTestParser(t, &grammar{}, participle.Lexer(lex), participle.CaseInsensitive("Keyword")) - actual := &grammar{} - err := p.ParseString("", `SELECT foo`, actual) + p := mustTestParser[grammar](t, participle.Lexer(lex), participle.CaseInsensitive("Keyword")) + actual, err := p.ParseString("", `SELECT foo`) expected := &grammar{"foo"} require.NoError(t, err) require.Equal(t, expected, actual) - actual = &grammar{} - err = p.ParseString("", `select foo`, actual) + actual, err = p.ParseString("", `select foo`) require.NoError(t, err) require.Equal(t, expected, actual) } @@ -929,9 +894,8 @@ func TestTokenAfterRepeatErrors(t *testing.T) { type grammar struct { Text string `@Ident* "foo"` } - p := mustTestParser(t, &grammar{}) - ast := &grammar{} - err := p.ParseString("", ``, ast) + p := mustTestParser[grammar](t) + _, err := p.ParseString("", ``) require.Error(t, err) } @@ -939,9 +903,8 @@ func TestEOFAfterRepeat(t *testing.T) { type grammar struct { Text string `@Ident*` } - p := mustTestParser(t, &grammar{}) - ast := &grammar{} - err := p.ParseString("", ``, ast) + p := mustTestParser[grammar](t) + _, err := p.ParseString("", ``) require.NoError(t, err) } @@ -949,87 +912,92 @@ func TestTrailing(t *testing.T) { type grammar struct { Text string `@Ident` } - p := mustTestParser(t, &grammar{}) - err := p.ParseString("", `foo bar`, &grammar{}) + p := mustTestParser[grammar](t) + _, err := p.ParseString("", `foo bar`) require.Error(t, err) } +type modifierTest[G any] struct { + name string + input string + expected string + fail bool +} + +func (test modifierTest[G]) test(t *testing.T) { + t.Helper() + t.Run(test.name, func(t *testing.T) { + p := mustTestParser[G](t) + grammar, err := p.ParseString("", test.input) + if test.fail { + require.Error(t, err) + } else { + require.NoError(t, err) + actual := reflect.ValueOf(grammar).Elem().FieldByName("A").String() + require.Equal(t, test.expected, actual) + } + }) +} + func TestModifiers(t *testing.T) { - nonEmptyGrammar := &struct { + type nonEmptyGrammar struct { A string `@( ("x"? "y"? "z"?)! "b" )` - }{} - tests := []struct { - name string - grammar interface{} - input string - expected string - fail bool - }{ - {name: "NonMatchingOptionalNonEmpty", - input: "b", - fail: true, - grammar: nonEmptyGrammar}, - {name: "NonEmptyMatch", + } + tests := []interface{ test(t *testing.T) }{ + modifierTest[nonEmptyGrammar]{name: "NonMatchingOptionalNonEmpty", + input: "b", + fail: true, + }, + modifierTest[nonEmptyGrammar]{name: "NonEmptyMatch", input: "x b", expected: "xb", - grammar: nonEmptyGrammar}, - {name: "NonEmptyMatchAll", + }, + modifierTest[nonEmptyGrammar]{name: "NonEmptyMatchAll", input: "x y z b", expected: "xyzb", - grammar: nonEmptyGrammar}, - {name: "NonEmptyMatchSome", + }, + modifierTest[nonEmptyGrammar]{name: "NonEmptyMatchSome", input: "x z b", expected: "xzb", - grammar: nonEmptyGrammar}, - {name: "MatchingOptional", + }, + modifierTest[struct { + A string `@( "a"? "b" )` + }]{name: "MatchingOptional", input: "a b", expected: "ab", - grammar: &struct { - A string `@( "a"? "b" )` - }{}}, - {name: "NonMatchingOptionalIsSkipped", + }, + modifierTest[struct { + A string `@( "a"? "b" )` + }]{name: "NonMatchingOptionalIsSkipped", input: "b", expected: "b", - grammar: &struct { - A string `@( "a"? "b" )` - }{}}, - {name: "MatchingOneOrMore", + }, + modifierTest[struct { + A string `@( "a"+ )` + }]{name: "MatchingOneOrMore", input: "a a a a a", expected: "aaaaa", - grammar: &struct { - A string `@( "a"+ )` - }{}}, - {name: "NonMatchingOneOrMore", + }, + modifierTest[struct { + A string `@( "a"+ )` + }]{name: "NonMatchingOneOrMore", input: "", fail: true, - grammar: &struct { - A string `@( "a"+ )` - }{}}, - {name: "MatchingZeroOrMore", + }, + modifierTest[struct { + A string `@( "a"* )` + }]{name: "MatchingZeroOrMore", input: "aaaaaaa", fail: true, - grammar: &struct { - A string `@( "a"* )` - }{}}, - {name: "NonMatchingZeroOrMore", + }, + modifierTest[struct { + A string `@( "a"* )` + }]{name: "NonMatchingZeroOrMore", input: "", - grammar: &struct { - A string `@( "a"* )` - }{}}, + }, } for _, test := range tests { - // nolint: scopelint - t.Run(test.name, func(t *testing.T) { - p := mustTestParser(t, test.grammar) - err := p.ParseString("", test.input, test.grammar) - if test.fail { - require.Error(t, err) - } else { - require.NoError(t, err) - actual := reflect.ValueOf(test.grammar).Elem().FieldByName("A").String() - require.Equal(t, test.expected, actual) - } - }) + test.test(t) } } @@ -1043,19 +1011,21 @@ func TestNonEmptyMatchWithOptionalGroup(t *testing.T) { End term `parser:" (':' @@)?)! ']'"` } - result := grammar{} - p := mustTestParser(t, &result) + p := mustTestParser[grammar](t) - require.NoError(t, p.ParseString("", "[-x]", &result)) - require.Equal(t, grammar{Start: term{Minus: true, Name: "x"}}, result) + result, err := p.ParseString("", "[-x]") + require.NoError(t, err) + require.Equal(t, &grammar{Start: term{Minus: true, Name: "x"}}, result) - require.NoError(t, p.ParseString("", "[a:-b]", &result)) - require.Equal(t, grammar{Start: term{Name: "a"}, End: term{Minus: true, Name: "b"}}, result) + result, err = p.ParseString("", "[a:-b]") + require.NoError(t, err) + require.Equal(t, &grammar{Start: term{Name: "a"}, End: term{Minus: true, Name: "b"}}, result) - require.NoError(t, p.ParseString("", "[:end]", &result)) - require.Equal(t, grammar{End: term{Name: "end"}}, result) + result, err = p.ParseString("", "[:end]") + require.NoError(t, err) + require.Equal(t, &grammar{End: term{Name: "end"}}, result) - err := p.ParseString("", "[]", &result) + _, err = p.ParseString("", "[]") require.EqualError(t, err, `1:2: sub-expression (Term? (":" Term)?)! cannot be empty`) } @@ -1063,7 +1033,7 @@ func TestIssue60(t *testing.T) { type grammar struct { A string `@("one" | | "two")` } - _, err := participle.Build(&grammar{}) + _, err := participle.Build[grammar]() require.Error(t, err) } @@ -1083,7 +1053,7 @@ type Issue62Foo struct { } func TestIssue62(t *testing.T) { - _, err := participle.Build(&Issue62Foo{}) + _, err := participle.Build[Issue62Foo]() require.NoError(t, err) } @@ -1096,7 +1066,7 @@ func TestIssue71(t *testing.T) { pattern *Sub `@@` } - _, err := participle.Build(&grammar{}) + _, err := participle.Build[grammar]() require.Error(t, err) } @@ -1105,13 +1075,12 @@ func TestAllowTrailing(t *testing.T) { Name string `@Ident` } - p, err := participle.Build(&G{}) + p, err := participle.Build[G]() require.NoError(t, err) - g := &G{} - err = p.ParseString("", `hello world`, g) + _, err = p.ParseString("", `hello world`) require.Error(t, err) - err = p.ParseString("", `hello world`, g, participle.AllowTrailing(true)) + g, err := p.ParseString("", `hello world`, participle.AllowTrailing(true)) require.NoError(t, err) require.Equal(t, &G{"hello"}, g) } @@ -1124,9 +1093,8 @@ func TestDisjunctionErrorReporting(t *testing.T) { type grammar struct { Statements []*statement `"{" ( @@ )* "}"` } - p := mustTestParser(t, &grammar{}) - ast := &grammar{} - err := p.ParseString("", `{ add foo }`, ast) + p := mustTestParser[grammar](t) + _, err := p.ParseString("", `{ add foo }`) // TODO: This should produce a more useful error. This is returned by sequence.Parse(). require.EqualError(t, err, `1:7: unexpected token "foo" (expected "}")`) } @@ -1137,11 +1105,10 @@ func TestCustomInt(t *testing.T) { Value MyInt `@Int` } - p, err := participle.Build(&G{}) + p, err := participle.Build[G]() require.NoError(t, err) - g := &G{} - err = p.ParseString("", `42`, g) + g, err := p.ParseString("", `42`) require.NoError(t, err) require.Equal(t, &G{42}, g) } @@ -1151,14 +1118,13 @@ func TestBoolIfSet(t *testing.T) { Value bool `@"true"?` } - p, err := participle.Build(&G{}) + p, err := participle.Build[G]() require.NoError(t, err) - g := &G{} - err = p.ParseString("", `true`, g) + g, err := p.ParseString("", `true`) require.NoError(t, err) require.Equal(t, &G{true}, g) - err = p.ParseString("", ``, g) + g, err = p.ParseString("", ``) require.NoError(t, err) require.Equal(t, &G{false}, g) } @@ -1169,14 +1135,13 @@ func TestCustomBoolIfSet(t *testing.T) { Value MyBool `@"true"?` } - p, err := participle.Build(&G{}) + p, err := participle.Build[G]() require.NoError(t, err) - g := &G{} - err = p.ParseString("", `true`, g) + g, err := p.ParseString("", `true`) require.NoError(t, err) require.Equal(t, &G{true}, g) - err = p.ParseString("", ``, g) + g, err = p.ParseString("", ``) require.NoError(t, err) require.Equal(t, &G{false}, g) } @@ -1185,9 +1150,8 @@ func TestPointerToList(t *testing.T) { type grammar struct { List *[]string `@Ident*` } - p := mustTestParser(t, &grammar{}) - ast := &grammar{} - err := p.ParseString("", `foo bar`, ast) + p := mustTestParser[grammar](t) + ast, err := p.ParseString("", `foo bar`) require.NoError(t, err) l := []string{"foo", "bar"} require.Equal(t, &grammar{List: &l}, ast) @@ -1199,7 +1163,7 @@ func TestPointerToList(t *testing.T) { // type grammar struct { // List []string `"{" @Ident* "}"` // } -// p := mustTestParser(t, &grammar{}) +// p := mustTestParser[grammar](t) // ast := &grammar{} // err := p.ParseString(`{}`, ast) // require.NoError(t, err) @@ -1210,13 +1174,12 @@ func TestNegation(t *testing.T) { type grammar struct { EverythingUntilSemicolon *[]string `@!';'* @';'` } - p := mustTestParser(t, &grammar{}) - ast := &grammar{} - err := p.ParseString("", `hello world ;`, ast) + p := mustTestParser[grammar](t) + ast, err := p.ParseString("", `hello world ;`) require.NoError(t, err) require.Equal(t, &[]string{"hello", "world", ";"}, ast.EverythingUntilSemicolon) - err = p.ParseString("", `hello world`, ast) + _, err = p.ParseString("", `hello world`) require.Error(t, err) } @@ -1225,20 +1188,19 @@ func TestNegationWithPattern(t *testing.T) { EverythingMoreComplex *[]string `@!(';' String)* @';' @String` } - p := mustTestParser(t, &grammar{}, participle.Unquote()) + p := mustTestParser[grammar](t, participle.Unquote()) // j, err := json.MarshalIndent(p.root, "", " ") // log.Print(j) // log.Print(ebnf(p.root)) - ast := &grammar{} - err := p.ParseString("", `hello world ; "some-str"`, ast) + ast, err := p.ParseString("", `hello world ; "some-str"`) require.NoError(t, err) require.Equal(t, &[]string{"hello", "world", ";", `some-str`}, ast.EverythingMoreComplex) - err = p.ParseString("", `hello ; world ; "hey"`, ast) + ast, err = p.ParseString("", `hello ; world ; "hey"`) require.NoError(t, err) require.Equal(t, &[]string{"hello", ";", "world", ";", `hey`}, ast.EverythingMoreComplex) - err = p.ParseString("", `hello ; world ;`, ast) + _, err = p.ParseString("", `hello ; world ;`) require.Error(t, err) } @@ -1248,13 +1210,12 @@ func TestNegationWithDisjunction(t *testing.T) { } // Note: we need more lookahead since (';' String) needs some before failing to match - p := mustTestParser(t, &grammar{}) - ast := &grammar{} - err := p.ParseString("", `hello world ;`, ast) + p := mustTestParser[grammar](t) + ast, err := p.ParseString("", `hello world ;`) require.NoError(t, err) require.Equal(t, &[]string{"hello", "world", ";"}, ast.EverythingMoreComplex) - err = p.ParseString("", `hello world , `, ast) + ast, err = p.ParseString("", `hello world , `) require.NoError(t, err) require.Equal(t, &[]string{"hello", "world", ","}, ast.EverythingMoreComplex) } @@ -1272,20 +1233,24 @@ func TestLookaheadGroup_Positive_SingleToken(t *testing.T) { Left val `@@` Ops []op `@@*` } - p := mustTestParser(t, &sum{}) - ast := &sum{} + p := mustTestParser[sum](t) - require.NoError(t, p.ParseString("", `"x" + "y" + 4`, ast)) + ast, err := p.ParseString("", `"x" + "y" + 4`) + require.NoError(t, err) require.Equal(t, &sum{Left: val{Str: `"x"`}, Ops: []op{{"+", val{Str: `"y"`}}, {"+", val{Int: 4}}}}, ast) - require.NoError(t, p.ParseString("", `"a" * 4 + "b"`, ast)) + ast, err = p.ParseString("", `"a" * 4 + "b"`) + require.NoError(t, err) require.Equal(t, &sum{Left: val{Str: `"a"`}, Ops: []op{{"*", val{Int: 4}}, {"+", val{Str: `"b"`}}}}, ast) - require.NoError(t, p.ParseString("", `1 * 2 * 3`, ast)) + ast, err = p.ParseString("", `1 * 2 * 3`) + require.NoError(t, err) require.Equal(t, &sum{Left: val{Int: 1}, Ops: []op{{"*", val{Int: 2}}, {"*", val{Int: 3}}}}, ast) - require.EqualError(t, p.ParseString("", `"a" * "x" + "b"`, ast), `1:7: unexpected '"x"'`) - require.EqualError(t, p.ParseString("", `4 * 2 + 0 * "b"`, ast), `1:13: unexpected '"b"'`) + _, err = p.ParseString("", `"a" * "x" + "b"`) + require.EqualError(t, err, `1:7: unexpected '"x"'`) + _, err = p.ParseString("", `4 * 2 + 0 * "b"`) + require.EqualError(t, err, `1:13: unexpected '"b"'`) } func TestLookaheadGroup_Negative_SingleToken(t *testing.T) { @@ -1296,25 +1261,27 @@ func TestLookaheadGroup_Negative_SingleToken(t *testing.T) { Identifiers []variable `((?! 'except'|'end') @@)*` Except *variable `('except' @@)? 'end'` } - p := mustTestParser(t, &grammar{}) - ast := &grammar{} + p := mustTestParser[grammar](t) - require.NoError(t, p.ParseString("", `one two three exception end`, ast)) + ast, err := p.ParseString("", `one two three exception end`) + require.NoError(t, err) require.Equal(t, []variable{{"one"}, {"two"}, {"three"}, {"exception"}}, ast.Identifiers) require.Zero(t, ast.Except) - require.NoError(t, p.ParseString("", `anything except this end`, ast)) + ast, err = p.ParseString("", `anything except this end`) + require.NoError(t, err) require.Equal(t, []variable{{"anything"}}, ast.Identifiers) require.Equal(t, &variable{"this"}, ast.Except) - require.NoError(t, p.ParseString("", `except the end`, ast)) + ast, err = p.ParseString("", `except the end`) + require.NoError(t, err) require.Zero(t, ast.Identifiers) require.Equal(t, &variable{"the"}, ast.Except) - err := p.ParseString("", `no ending`, ast) + _, err = p.ParseString("", `no ending`) require.EqualError(t, err, `1:10: unexpected token "" (expected "end")`) - err = p.ParseString("", `no end in sight`, ast) + _, err = p.ParseString("", `no end in sight`) require.EqualError(t, err, `1:8: unexpected token "in"`) } @@ -1322,19 +1289,21 @@ func TestLookaheadGroup_Negative_MultipleTokens(t *testing.T) { type grammar struct { Parts []string `((?! '.' '.' '.') @(Ident | '.'))*` } - p := mustTestParser(t, &grammar{}) - ast := &grammar{} + p := mustTestParser[grammar](t) - require.NoError(t, p.ParseString("", `x.y.z.`, ast)) + ast, err := p.ParseString("", `x.y.z.`) + require.NoError(t, err) require.Equal(t, []string{"x", ".", "y", ".", "z", "."}, ast.Parts) - require.NoError(t, p.ParseString("", `..x..`, ast)) + ast, err = p.ParseString("", `..x..`) + require.NoError(t, err) require.Equal(t, []string{".", ".", "x", ".", "."}, ast.Parts) - require.NoError(t, p.ParseString("", `two.. are fine`, ast)) + ast, err = p.ParseString("", `two.. are fine`) + require.NoError(t, err) require.Equal(t, []string{"two", ".", ".", "are", "fine"}, ast.Parts) - err := p.ParseString("", `but this... is just wrong`, ast) + _, err = p.ParseString("", `but this... is just wrong`) require.EqualError(t, err, `1:9: unexpected token "."`) } @@ -1351,14 +1320,13 @@ func TestASTTokens(t *testing.T) { Subject subject `"hello" @@` } - p := mustTestParser(t, &hello{}, + p := mustTestParser[hello](t, participle.Elide("Whitespace"), participle.Lexer(lexer.MustSimple([]lexer.SimpleRule{ {"Ident", `\w+`}, {"Whitespace", `\s+`}, }))) - actual := &hello{} - err := p.ParseString("", "hello world", actual) + actual, err := p.ParseString("", "hello world") require.NoError(t, err) tokens := []lexer.Token{ {-2, "hello", lexer.Position{Line: 1, Column: 1}}, @@ -1381,9 +1349,8 @@ func TestCaptureIntoToken(t *testing.T) { Tail []lexer.Token `@(Ident*)` } - p := mustTestParser(t, &ast{}) - actual := &ast{} - err := p.ParseString("", "hello waz baz", actual) + p := mustTestParser[ast](t) + actual, err := p.ParseString("", "hello waz baz") require.NoError(t, err) expected := &ast{ Head: lexer.Token{-2, "hello", lexer.Position{Line: 1, Column: 1}}, @@ -1415,15 +1382,13 @@ func TestEndPos(t *testing.T) { }, })) - Parser = participle.MustBuild( - &AST{}, + Parser = participle.MustBuild[AST]( participle.Lexer(Lexer), participle.Elide("Whitespace"), ) ) - mod := &AST{} - err := Parser.Parse("", strings.NewReader("foo bar"), mod) + mod, err := Parser.Parse("", strings.NewReader("foo bar")) require.NoError(t, err) require.Equal(t, 0, mod.First.Pos.Offset) require.Equal(t, 3, mod.First.EndPos.Offset) @@ -1452,8 +1417,7 @@ func TestBug(t *testing.T) { {"Whitespace", `[\r\t ]+`, nil}, }, })) - parser = participle.MustBuild( - &AST{}, + parser = participle.MustBuild[AST]( participle.Lexer(lexer), participle.Elide("Whitespace"), ) @@ -1465,8 +1429,7 @@ func TestBug(t *testing.T) { Diff: "!", }, } - actual := &AST{} - err := parser.Parse("name", strings.NewReader(`branch 1!`), actual) + actual, err := parser.Parse("name", strings.NewReader(`branch 1!`)) require.NoError(t, err) require.Equal(t, expected, actual) } @@ -1485,7 +1448,7 @@ func TestCaptureOnSliceElements(t *testing.T) { // nolint:dupl SlicePtr []*sliceCapture `@Capture @Capture` } - parser := participle.MustBuild(&capture{}, []participle.Option{ + parser := participle.MustBuild[capture]([]participle.Option{ participle.Lexer(lexer.MustSimple([]lexer.SimpleRule{ {Name: "Capture", Pattern: `[a-z]{3}`}, {Name: "Whitespace", Pattern: `[\s|\n]+`}, @@ -1493,8 +1456,8 @@ func TestCaptureOnSliceElements(t *testing.T) { // nolint:dupl participle.Elide("Whitespace"), }...) - captured := &capture{} - require.NoError(t, parser.ParseString("capture_slice", `abc def ijk lmn opq`, captured)) + captured, err := parser.ParseString("capture_slice", `abc def ijk lmn opq`) + require.NoError(t, err) expectedSingle := sliceCapture("ABC") expectedSlicePtr1 := sliceCapture("LMN") @@ -1526,7 +1489,7 @@ func TestParseOnSliceElements(t *testing.T) { // nolint:dupl Slice []sliceParse `@@+` } - parser := participle.MustBuild(&parse{}, []participle.Option{ + parser := participle.MustBuild[parse]([]participle.Option{ participle.Lexer(lexer.MustSimple([]lexer.SimpleRule{ {Name: "Element", Pattern: `[a-z]{3}`}, {Name: "Whitespace", Pattern: `[\s|\n]+`}, @@ -1534,8 +1497,8 @@ func TestParseOnSliceElements(t *testing.T) { // nolint:dupl participle.Elide("Whitespace"), }...) - parsed := &parse{} - require.NoError(t, parser.ParseString("parse_slice", `abc def ijk`, parsed)) + parsed, err := parser.ParseString("parse_slice", `abc def ijk`) + require.NoError(t, err) expectedSingle := sliceParse("abcabc") expected := &parse{ @@ -1551,11 +1514,10 @@ func TestUnmarshalNetIP(t *testing.T) { IP net.IP `@IP` } - parser := mustTestParser(t, &grammar{}, participle.Lexer(lexer.MustSimple([]lexer.SimpleRule{ + parser := mustTestParser[grammar](t, participle.Lexer(lexer.MustSimple([]lexer.SimpleRule{ {"IP", `[\d.]+`}, }))) - ast := &grammar{} - err := parser.ParseString("", "10.2.3.4", ast) + ast, err := parser.ParseString("", "10.2.3.4") require.NoError(t, err) require.Equal(t, "10.2.3.4", ast.IP.String()) } @@ -1573,11 +1535,10 @@ func TestCaptureIP(t *testing.T) { IP Address `@IP` } - parser := mustTestParser(t, &grammar{}, participle.Lexer(lexer.MustSimple([]lexer.SimpleRule{ + parser := mustTestParser[grammar](t, participle.Lexer(lexer.MustSimple([]lexer.SimpleRule{ {"IP", `[\d.]+`}, }))) - ast := &grammar{} - err := parser.ParseString("", "10.2.3.4", ast) + ast, err := parser.ParseString("", "10.2.3.4") require.NoError(t, err) require.Equal(t, "10.2.3.4", (net.IP)(ast.IP).String()) } @@ -1595,13 +1556,12 @@ func BenchmarkIssue143(b *testing.B) { List []Disjunction `parser:"@@*"` } - var disjunctionParser = participle.MustBuild(&Disjunctions{}) + var disjunctionParser = participle.MustBuild[Disjunctions]() input := " " - ast := &Disjunctions{} b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - if err := disjunctionParser.ParseString("", input, ast); err != nil { + if _, err := disjunctionParser.ParseString("", input); err != nil { panic(err) } } @@ -1628,13 +1588,12 @@ func TestBoxedCapture(t *testing.T) { {"whitespace", `\s+`}, }) - parser := participle.MustBuild(&Boxes{}, + parser := participle.MustBuild[Boxes]( participle.Lexer(lex), participle.UseLookahead(2), ) - boxed := &Boxes{} - if err := parser.ParseString("test", "abc::cdef.abc", boxed); err != nil { + if _, err := parser.ParseString("test", "abc::cdef.abc"); err != nil { t.Fatal(err) } } @@ -1643,11 +1602,10 @@ func TestMatchEOF(t *testing.T) { type testMatchNewlineOrEOF struct { Text []string `@Ident+ ("\n" | EOF)` } - p := mustTestParser(t, &testMatchNewlineOrEOF{}) - ast := &testMatchNewlineOrEOF{} - err := p.ParseString("", "hell world", ast) + p := mustTestParser[testMatchNewlineOrEOF](t) + _, err := p.ParseString("", "hell world") require.NoError(t, err) - err = p.ParseString("", "hell world\n", ast) + _, err = p.ParseString("", "hell world\n") require.NoError(t, err) } @@ -1661,17 +1619,15 @@ func TestParseExplicitElidedIdent(t *testing.T) { // nolint Comment string `@Comment?` Ident string `@Ident` } - p := mustTestParser(t, &grammar{}, participle.Lexer(lex), participle.Elide("Comment")) + p := mustTestParser[grammar](t, participle.Lexer(lex), participle.Elide("Comment")) - actual := grammar{} - err := p.ParseString("", `hello`, &actual) + actual, err := p.ParseString("", `hello`) require.NoError(t, err) - require.Equal(t, grammar{Ident: "hello"}, actual) + require.Equal(t, &grammar{Ident: "hello"}, actual) - actual = grammar{} - err = p.ParseString("", `/* Comment */ hello`, &actual) + actual, err = p.ParseString("", `/* Comment */ hello`) require.NoError(t, err) - require.Equal(t, grammar{Comment: `/* Comment */`, Ident: "hello"}, actual) + require.Equal(t, &grammar{Comment: `/* Comment */`, Ident: "hello"}, actual) } func TestParseExplicitElidedTypedLiteral(t *testing.T) { // nolint @@ -1684,17 +1640,15 @@ func TestParseExplicitElidedTypedLiteral(t *testing.T) { // nolint Comment string `@"/* Comment */":Comment?` Ident string `@Ident` } - p := mustTestParser(t, &grammar{}, participle.Lexer(lex), participle.Elide("Comment")) + p := mustTestParser[grammar](t, participle.Lexer(lex), participle.Elide("Comment")) - actual := grammar{} - err := p.ParseString("", `hello`, &actual) + actual, err := p.ParseString("", `hello`) require.NoError(t, err) - require.Equal(t, grammar{Ident: "hello"}, actual) + require.Equal(t, &grammar{Ident: "hello"}, actual) - actual = grammar{} - err = p.ParseString("", `/* Comment */ hello`, &actual) + actual, err = p.ParseString("", `/* Comment */ hello`) require.NoError(t, err) - require.Equal(t, grammar{Comment: `/* Comment */`, Ident: "hello"}, actual) + require.Equal(t, &grammar{Comment: `/* Comment */`, Ident: "hello"}, actual) } func TestEmptySequenceMatches(t *testing.T) { @@ -1707,10 +1661,9 @@ func TestEmptySequenceMatches(t *testing.T) { Ident []string `@Ident*` Comments []string `@Comment*` } - p := mustTestParser(t, &grammar{}, participle.Lexer(lex), participle.Elide("Whitespace")) - actual := grammar{} - expected := grammar{} - err := p.ParseString("", "", &actual) + p := mustTestParser[grammar](t, participle.Lexer(lex), participle.Elide("Whitespace")) + expected := &grammar{} + actual, err := p.ParseString("", "") require.NoError(t, err) require.Equal(t, expected, actual) } @@ -1724,8 +1677,8 @@ func (*RootParseableFail) Parse(lex *lexer.PeekingLexer) error { } func TestRootParseableFail(t *testing.T) { - p := mustTestParser(t, &RootParseableFail{}) - err := p.ParseString("", "blah", &RootParseableFail{}) + p := mustTestParser[RootParseableFail](t) + _, err := p.ParseString("", "blah") require.EqualError(t, err, ":1:1: always fail immediately") } @@ -1746,7 +1699,7 @@ func TestParserWithCustomProduction(t *testing.T) { Custom TestCustom `@@` } - p := mustTestParser(t, &grammar{}, participle.ParseTypeWith(func(lex *lexer.PeekingLexer) (TestCustom, error) { + p := mustTestParser[grammar](t, participle.ParseTypeWith(func(lex *lexer.PeekingLexer) (TestCustom, error) { switch peek := lex.Peek(); { case peek.Type == scanner.Int || peek.Type == scanner.Float: v, err := strconv.ParseFloat(lex.Next().Value, 64) @@ -1776,8 +1729,8 @@ func TestParserWithCustomProduction(t *testing.T) { {"true", CustomBoolean(true)}, {"false", CustomBoolean(false)}, } { - var actual grammar - require.NoError(t, p.ParseString("", c.src, &actual)) + actual, err := p.ParseString("", c.src) + require.NoError(t, err) require.Equal(t, c.expected, actual.Custom) } @@ -1817,7 +1770,7 @@ func TestParserWithUnion(t *testing.T) { B TestUnionB `| @@` } - parser := mustTestParser(t, &grammar{}, participle.UseLookahead(10), + parser := mustTestParser[grammar](t, participle.UseLookahead(10), participle.Union[TestUnionA](AMember1{}, AMember2{}), participle.Union[TestUnionB](BMember1{}, BMember2{})) @@ -1833,10 +1786,10 @@ func TestParserWithUnion(t *testing.T) { {`{x}`, grammar{B: BMember2{AMember1{"x"}}}}, {`{ [ { [12] } ] }`, grammar{B: BMember2{AMember2{BMember2{AMember2{BMember1{12}}}}}}}, } { - var actual grammar var trace strings.Builder - require.NoError(t, parser.ParseString("", c.src, &actual, participle.Trace(&trace))) - require.Equal(t, c.expected, actual) + actual, err := parser.ParseString("", c.src, participle.Trace(&trace)) + require.NoError(t, err) + require.Equal(t, &c.expected, actual) require.NotEqual(t, "", trace.String()) } @@ -1866,29 +1819,26 @@ func TestParseSubProduction(t *testing.T) { numberItem := func(n float64) ListItem { return ListItem{Number: &n} } stringItem := func(s string) ListItem { return ListItem{String: &s} } - p := mustTestParser(t, &Grammar{}, participle.Unquote()) + p := mustTestParser[Grammar](t, participle.Unquote()) - var ( - actual Grammar - expected = Grammar{List: []ListItem{numberItem(1), stringItem("test")}} - ) + expected := &Grammar{List: []ListItem{numberItem(1), stringItem("test")}} - require.NoError(t, p.ParseString("", `[ 1, "test" ]`, &actual)) + actual, err := p.ParseString("", `[ 1, "test" ]`) + require.NoError(t, err) require.Equal(t, expected, actual) - var ( - actualItem ListItem - expectedItem = numberItem(1.234e5) - ) + expectedItem := numberItem(1.234e5) - require.NoError(t, p.ParseString("", `1.234e5`, &actualItem)) - require.Equal(t, expectedItem, actualItem) + ip, err := participle.ParserForProduction[ListItem](p) + require.NoError(t, err) - var ( - actualItem2 ListItem - expectedItem2 = stringItem("\t\ttest\t\t") - ) + actualItem, err := ip.ParseString("", `1.234e5`) + require.NoError(t, err) + require.Equal(t, &expectedItem, actualItem) + + expectedItem2 := stringItem("\t\ttest\t\t") - require.NoError(t, p.ParseString("", `"\t\ttest\t\t"`, &actualItem2)) - require.Equal(t, expectedItem2, actualItem2) + actualItem2, err := ip.ParseString("", `"\t\ttest\t\t"`) + require.NoError(t, err) + require.Equal(t, &expectedItem2, actualItem2) } diff --git a/validate_test.go b/validate_test.go index 96b23ab7..a26d846d 100644 --- a/validate_test.go +++ b/validate_test.go @@ -13,7 +13,7 @@ type leftRecursionSimple struct { } func TestValidateLeftRecursion(t *testing.T) { - _, err := participle.Build(&leftRecursionSimple{}) + _, err := participle.Build[leftRecursionSimple]() require.Error(t, err) require.Equal(t, err.Error(), `left recursion detected on @@ -31,7 +31,7 @@ type leftRecursionNested struct { } func TestValidateLeftRecursionNested(t *testing.T) { - _, err := participle.Build(&leftRecursionNested{}) + _, err := participle.Build[leftRecursionNested]() require.Error(t, err) require.Equal(t, err.Error(), `left recursion detected on