diff --git a/asciidoc/parse/asciidoc.go b/asciidoc/parse/asciidoc.go index fa0f3cb6..50d971aa 100644 --- a/asciidoc/parse/asciidoc.go +++ b/asciidoc/parse/asciidoc.go @@ -6203,38 +6203,38 @@ var g = &grammar{ pos: position{line: 853, col: 16, offset: 26034}, expr: &charClassMatcher{ pos: position{line: 853, col: 17, offset: 26035}, - val: "[^\\r\\n{.<>!?,;[\\] ]", - chars: []rune{'\r', '\n', '{', '.', '<', '>', '!', '?', ',', ';', '[', ']', ' '}, + val: "[^\\n{.<>!?,;[\\] ]", + chars: []rune{'\n', '{', '.', '<', '>', '!', '?', ',', ';', '[', ']', ' '}, ignoreCase: false, inverted: true, }, }, &seqExpr{ - pos: position{line: 854, col: 5, offset: 26062}, + pos: position{line: 854, col: 5, offset: 26060}, exprs: []any{ &charClassMatcher{ - pos: position{line: 854, col: 5, offset: 26062}, + pos: position{line: 854, col: 5, offset: 26060}, val: "[.?!;,]", chars: []rune{'.', '?', '!', ';', ','}, ignoreCase: false, inverted: false, }, &andExpr{ - pos: position{line: 854, col: 13, offset: 26070}, + pos: position{line: 854, col: 13, offset: 26068}, expr: &seqExpr{ - pos: position{line: 854, col: 15, offset: 26072}, + pos: position{line: 854, col: 15, offset: 26070}, exprs: []any{ ¬Expr{ - pos: position{line: 854, col: 15, offset: 26072}, + pos: position{line: 854, col: 15, offset: 26070}, expr: &ruleRefExpr{ - pos: position{line: 854, col: 16, offset: 26073}, + pos: position{line: 854, col: 16, offset: 26071}, offset: 308, }, }, ¬Expr{ - pos: position{line: 854, col: 18, offset: 26075}, + pos: position{line: 854, col: 18, offset: 26073}, expr: &ruleRefExpr{ - pos: position{line: 854, col: 19, offset: 26076}, + pos: position{line: 854, col: 19, offset: 26074}, offset: 294, }, }, @@ -6244,7 +6244,7 @@ var g = &grammar{ }, }, &ruleRefExpr{ - pos: position{line: 855, col: 4, offset: 26091}, + pos: position{line: 855, col: 4, offset: 26089}, offset: 41, }, }, @@ -6252,32 +6252,32 @@ var g = &grammar{ }, { name: "Email", - pos: position{line: 859, col: 1, offset: 26115}, + pos: position{line: 859, col: 1, offset: 26113}, expr: &actionExpr{ - pos: position{line: 859, col: 9, offset: 26123}, + pos: position{line: 859, col: 9, offset: 26121}, run: (*parser).callonEmail1, expr: &seqExpr{ - pos: position{line: 859, col: 9, offset: 26123}, + pos: position{line: 859, col: 9, offset: 26121}, exprs: []any{ &labeledExpr{ - pos: position{line: 859, col: 9, offset: 26123}, + pos: position{line: 859, col: 9, offset: 26121}, label: "name", expr: &ruleRefExpr{ - pos: position{line: 859, col: 15, offset: 26129}, + pos: position{line: 859, col: 15, offset: 26127}, offset: 149, }, }, &litMatcher{ - pos: position{line: 859, col: 26, offset: 26140}, + pos: position{line: 859, col: 26, offset: 26138}, val: "@", ignoreCase: false, want: "\"@\"", }, &labeledExpr{ - pos: position{line: 859, col: 30, offset: 26144}, + pos: position{line: 859, col: 30, offset: 26142}, label: "domain", expr: &ruleRefExpr{ - pos: position{line: 859, col: 38, offset: 26152}, + pos: position{line: 859, col: 38, offset: 26150}, offset: 150, }, }, @@ -6287,30 +6287,30 @@ var g = &grammar{ }, { name: "EmailName", - pos: position{line: 863, col: 1, offset: 26250}, + pos: position{line: 863, col: 1, offset: 26248}, expr: &actionExpr{ - pos: position{line: 863, col: 13, offset: 26262}, + pos: position{line: 863, col: 13, offset: 26260}, run: (*parser).callonEmailName1, expr: &seqExpr{ - pos: position{line: 863, col: 13, offset: 26262}, + pos: position{line: 863, col: 13, offset: 26260}, exprs: []any{ &labeledExpr{ - pos: position{line: 863, col: 13, offset: 26262}, + pos: position{line: 863, col: 13, offset: 26260}, label: "name", expr: &actionExpr{ - pos: position{line: 863, col: 19, offset: 26268}, + pos: position{line: 863, col: 19, offset: 26266}, run: (*parser).callonEmailName4, expr: &oneOrMoreExpr{ - pos: position{line: 863, col: 19, offset: 26268}, + pos: position{line: 863, col: 19, offset: 26266}, expr: &choiceExpr{ - pos: position{line: 863, col: 20, offset: 26269}, + pos: position{line: 863, col: 20, offset: 26267}, alternatives: []any{ &ruleRefExpr{ - pos: position{line: 863, col: 20, offset: 26269}, + pos: position{line: 863, col: 20, offset: 26267}, offset: 293, }, &charClassMatcher{ - pos: position{line: 863, col: 35, offset: 26284}, + pos: position{line: 863, col: 35, offset: 26282}, val: "[!#$%&'*+-/=?^_`{|}~.]", chars: []rune{'!', '#', '$', '%', '&', '\'', '*', '=', '?', '^', '_', '`', '{', '|', '}', '~', '.'}, ranges: []rune{'+', '/'}, @@ -6323,7 +6323,7 @@ var g = &grammar{ }, }, &andCodeExpr{ - pos: position{line: 865, col: 4, offset: 26412}, + pos: position{line: 865, col: 4, offset: 26410}, run: (*parser).callonEmailName9, }, }, @@ -6332,51 +6332,51 @@ var g = &grammar{ }, { name: "EmailDomain", - pos: position{line: 872, col: 1, offset: 26578}, + pos: position{line: 872, col: 1, offset: 26576}, expr: &actionExpr{ - pos: position{line: 872, col: 15, offset: 26592}, + pos: position{line: 872, col: 15, offset: 26590}, run: (*parser).callonEmailDomain1, expr: &seqExpr{ - pos: position{line: 872, col: 15, offset: 26592}, + pos: position{line: 872, col: 15, offset: 26590}, exprs: []any{ &labeledExpr{ - pos: position{line: 872, col: 15, offset: 26592}, + pos: position{line: 872, col: 15, offset: 26590}, label: "domain", expr: &actionExpr{ - pos: position{line: 872, col: 23, offset: 26600}, + pos: position{line: 872, col: 23, offset: 26598}, run: (*parser).callonEmailDomain4, expr: &seqExpr{ - pos: position{line: 872, col: 24, offset: 26601}, + pos: position{line: 872, col: 24, offset: 26599}, exprs: []any{ &oneOrMoreExpr{ - pos: position{line: 872, col: 24, offset: 26601}, + pos: position{line: 872, col: 24, offset: 26599}, expr: &ruleRefExpr{ - pos: position{line: 872, col: 25, offset: 26602}, + pos: position{line: 872, col: 25, offset: 26600}, offset: 293, }, }, &zeroOrMoreExpr{ - pos: position{line: 872, col: 40, offset: 26617}, + pos: position{line: 872, col: 40, offset: 26615}, expr: &seqExpr{ - pos: position{line: 872, col: 41, offset: 26618}, + pos: position{line: 872, col: 41, offset: 26616}, exprs: []any{ &litMatcher{ - pos: position{line: 872, col: 41, offset: 26618}, + pos: position{line: 872, col: 41, offset: 26616}, val: ".", ignoreCase: false, want: "\".\"", }, &oneOrMoreExpr{ - pos: position{line: 872, col: 45, offset: 26622}, + pos: position{line: 872, col: 45, offset: 26620}, expr: &choiceExpr{ - pos: position{line: 872, col: 46, offset: 26623}, + pos: position{line: 872, col: 46, offset: 26621}, alternatives: []any{ &ruleRefExpr{ - pos: position{line: 872, col: 46, offset: 26623}, + pos: position{line: 872, col: 46, offset: 26621}, offset: 293, }, &litMatcher{ - pos: position{line: 872, col: 61, offset: 26638}, + pos: position{line: 872, col: 61, offset: 26636}, val: "-", ignoreCase: false, want: "\"-\"", @@ -6392,7 +6392,7 @@ var g = &grammar{ }, }, &andCodeExpr{ - pos: position{line: 874, col: 4, offset: 26683}, + pos: position{line: 874, col: 4, offset: 26681}, run: (*parser).callonEmailDomain15, }, }, @@ -6401,34 +6401,34 @@ var g = &grammar{ }, { name: "Listing", - pos: position{line: 882, col: 1, offset: 26825}, + pos: position{line: 882, col: 1, offset: 26823}, expr: &actionExpr{ - pos: position{line: 883, col: 5, offset: 26839}, + pos: position{line: 883, col: 5, offset: 26837}, run: (*parser).callonListing1, expr: &seqExpr{ - pos: position{line: 883, col: 5, offset: 26839}, + pos: position{line: 883, col: 5, offset: 26837}, exprs: []any{ &labeledExpr{ - pos: position{line: 883, col: 5, offset: 26839}, + pos: position{line: 883, col: 5, offset: 26837}, label: "start", expr: &ruleRefExpr{ - pos: position{line: 883, col: 12, offset: 26846}, + pos: position{line: 883, col: 12, offset: 26844}, offset: 152, }, }, &labeledExpr{ - pos: position{line: 884, col: 5, offset: 26864}, + pos: position{line: 884, col: 5, offset: 26862}, label: "lines", expr: &zeroOrMoreExpr{ - pos: position{line: 884, col: 11, offset: 26870}, + pos: position{line: 884, col: 11, offset: 26868}, expr: &ruleRefExpr{ - pos: position{line: 884, col: 12, offset: 26871}, + pos: position{line: 884, col: 12, offset: 26869}, offset: 156, }, }, }, &ruleRefExpr{ - pos: position{line: 885, col: 5, offset: 26889}, + pos: position{line: 885, col: 5, offset: 26887}, offset: 153, }, }, @@ -6437,31 +6437,31 @@ var g = &grammar{ }, { name: "ListingStart", - pos: position{line: 889, col: 1, offset: 27047}, + pos: position{line: 889, col: 1, offset: 27045}, expr: &actionExpr{ - pos: position{line: 889, col: 16, offset: 27062}, + pos: position{line: 889, col: 16, offset: 27060}, run: (*parser).callonListingStart1, expr: &seqExpr{ - pos: position{line: 889, col: 16, offset: 27062}, + pos: position{line: 889, col: 16, offset: 27060}, exprs: []any{ &ruleRefExpr{ - pos: position{line: 889, col: 16, offset: 27062}, + pos: position{line: 889, col: 16, offset: 27060}, offset: 296, }, &labeledExpr{ - pos: position{line: 889, col: 32, offset: 27078}, + pos: position{line: 889, col: 32, offset: 27076}, label: "delimiter", expr: &ruleRefExpr{ - pos: position{line: 889, col: 42, offset: 27088}, + pos: position{line: 889, col: 42, offset: 27086}, offset: 155, }, }, &ruleRefExpr{ - pos: position{line: 889, col: 59, offset: 27105}, + pos: position{line: 889, col: 59, offset: 27103}, offset: 299, }, &andCodeExpr{ - pos: position{line: 889, col: 69, offset: 27115}, + pos: position{line: 889, col: 69, offset: 27113}, run: (*parser).callonListingStart7, }, }, @@ -6470,31 +6470,31 @@ var g = &grammar{ }, { name: "ListingEnd", - pos: position{line: 896, col: 1, offset: 27292}, + pos: position{line: 896, col: 1, offset: 27290}, expr: &seqExpr{ - pos: position{line: 896, col: 14, offset: 27305}, + pos: position{line: 896, col: 14, offset: 27303}, exprs: []any{ &ruleRefExpr{ - pos: position{line: 896, col: 14, offset: 27305}, + pos: position{line: 896, col: 14, offset: 27303}, offset: 296, }, &labeledExpr{ - pos: position{line: 896, col: 30, offset: 27321}, + pos: position{line: 896, col: 30, offset: 27319}, label: "delimiter", expr: &ruleRefExpr{ - pos: position{line: 896, col: 40, offset: 27331}, + pos: position{line: 896, col: 40, offset: 27329}, offset: 155, }, }, &andExpr{ - pos: position{line: 896, col: 57, offset: 27348}, + pos: position{line: 896, col: 57, offset: 27346}, expr: &ruleRefExpr{ - pos: position{line: 896, col: 58, offset: 27349}, + pos: position{line: 896, col: 58, offset: 27347}, offset: 299, }, }, &andCodeExpr{ - pos: position{line: 896, col: 68, offset: 27359}, + pos: position{line: 896, col: 68, offset: 27357}, run: (*parser).callonListingEnd7, }, }, @@ -6502,29 +6502,29 @@ var g = &grammar{ }, { name: "ListingHyphens", - pos: position{line: 905, col: 1, offset: 27619}, + pos: position{line: 905, col: 1, offset: 27617}, expr: &actionExpr{ - pos: position{line: 905, col: 18, offset: 27636}, + pos: position{line: 905, col: 18, offset: 27634}, run: (*parser).callonListingHyphens1, expr: &seqExpr{ - pos: position{line: 905, col: 18, offset: 27636}, + pos: position{line: 905, col: 18, offset: 27634}, exprs: []any{ &labeledExpr{ - pos: position{line: 905, col: 18, offset: 27636}, + pos: position{line: 905, col: 18, offset: 27634}, label: "hyphens", expr: &seqExpr{ - pos: position{line: 905, col: 27, offset: 27645}, + pos: position{line: 905, col: 27, offset: 27643}, exprs: []any{ &litMatcher{ - pos: position{line: 905, col: 27, offset: 27645}, + pos: position{line: 905, col: 27, offset: 27643}, val: "----", ignoreCase: false, want: "\"----\"", }, &zeroOrMoreExpr{ - pos: position{line: 905, col: 34, offset: 27652}, + pos: position{line: 905, col: 34, offset: 27650}, expr: &litMatcher{ - pos: position{line: 905, col: 34, offset: 27652}, + pos: position{line: 905, col: 34, offset: 27650}, val: "-", ignoreCase: false, want: "\"-\"", @@ -6534,7 +6534,7 @@ var g = &grammar{ }, }, &andCodeExpr{ - pos: position{line: 905, col: 40, offset: 27658}, + pos: position{line: 905, col: 40, offset: 27656}, run: (*parser).callonListingHyphens8, }, }, @@ -6543,23 +6543,23 @@ var g = &grammar{ }, { name: "ListingDelimiter", - pos: position{line: 912, col: 1, offset: 27777}, + pos: position{line: 912, col: 1, offset: 27775}, expr: &actionExpr{ - pos: position{line: 912, col: 20, offset: 27796}, + pos: position{line: 912, col: 20, offset: 27794}, run: (*parser).callonListingDelimiter1, expr: &seqExpr{ - pos: position{line: 912, col: 20, offset: 27796}, + pos: position{line: 912, col: 20, offset: 27794}, exprs: []any{ &labeledExpr{ - pos: position{line: 912, col: 20, offset: 27796}, + pos: position{line: 912, col: 20, offset: 27794}, label: "hyphens", expr: &ruleRefExpr{ - pos: position{line: 912, col: 29, offset: 27805}, + pos: position{line: 912, col: 29, offset: 27803}, offset: 154, }, }, &ruleRefExpr{ - pos: position{line: 913, col: 5, offset: 27826}, + pos: position{line: 913, col: 5, offset: 27824}, offset: 303, }, }, @@ -6568,34 +6568,34 @@ var g = &grammar{ }, { name: "ListingLine", - pos: position{line: 918, col: 1, offset: 27953}, + pos: position{line: 918, col: 1, offset: 27951}, expr: &actionExpr{ - pos: position{line: 918, col: 15, offset: 27967}, + pos: position{line: 918, col: 15, offset: 27965}, run: (*parser).callonListingLine1, expr: &seqExpr{ - pos: position{line: 918, col: 16, offset: 27968}, + pos: position{line: 918, col: 16, offset: 27966}, exprs: []any{ ¬Expr{ - pos: position{line: 918, col: 16, offset: 27968}, + pos: position{line: 918, col: 16, offset: 27966}, expr: &ruleRefExpr{ - pos: position{line: 918, col: 17, offset: 27969}, + pos: position{line: 918, col: 17, offset: 27967}, offset: 155, }, }, &ruleRefExpr{ - pos: position{line: 918, col: 34, offset: 27986}, + pos: position{line: 918, col: 34, offset: 27984}, offset: 295, }, &labeledExpr{ - pos: position{line: 918, col: 47, offset: 27999}, + pos: position{line: 918, col: 47, offset: 27997}, label: "line", expr: &ruleRefExpr{ - pos: position{line: 918, col: 52, offset: 28004}, + pos: position{line: 918, col: 52, offset: 28002}, offset: 302, }, }, &ruleRefExpr{ - pos: position{line: 918, col: 60, offset: 28012}, + pos: position{line: 918, col: 60, offset: 28010}, offset: 299, }, }, @@ -6604,38 +6604,38 @@ var g = &grammar{ }, { name: "Open", - pos: position{line: 922, col: 1, offset: 28063}, + pos: position{line: 922, col: 1, offset: 28061}, expr: &actionExpr{ - pos: position{line: 923, col: 5, offset: 28074}, + pos: position{line: 923, col: 5, offset: 28072}, run: (*parser).callonOpen1, expr: &seqExpr{ - pos: position{line: 923, col: 5, offset: 28074}, + pos: position{line: 923, col: 5, offset: 28072}, exprs: []any{ &labeledExpr{ - pos: position{line: 923, col: 5, offset: 28074}, + pos: position{line: 923, col: 5, offset: 28072}, label: "start", expr: &ruleRefExpr{ - pos: position{line: 923, col: 12, offset: 28081}, + pos: position{line: 923, col: 12, offset: 28079}, offset: 158, }, }, &labeledExpr{ - pos: position{line: 924, col: 5, offset: 28096}, + pos: position{line: 924, col: 5, offset: 28094}, label: "lines", expr: &zeroOrMoreExpr{ - pos: position{line: 924, col: 11, offset: 28102}, + pos: position{line: 924, col: 11, offset: 28100}, expr: &seqExpr{ - pos: position{line: 924, col: 12, offset: 28103}, + pos: position{line: 924, col: 12, offset: 28101}, exprs: []any{ ¬Expr{ - pos: position{line: 924, col: 12, offset: 28103}, + pos: position{line: 924, col: 12, offset: 28101}, expr: &ruleRefExpr{ - pos: position{line: 924, col: 13, offset: 28104}, + pos: position{line: 924, col: 13, offset: 28102}, offset: 161, }, }, &ruleRefExpr{ - pos: position{line: 924, col: 27, offset: 28118}, + pos: position{line: 924, col: 27, offset: 28116}, offset: 1, }, }, @@ -6643,7 +6643,7 @@ var g = &grammar{ }, }, &ruleRefExpr{ - pos: position{line: 925, col: 5, offset: 28132}, + pos: position{line: 925, col: 5, offset: 28130}, offset: 159, }, }, @@ -6652,27 +6652,27 @@ var g = &grammar{ }, { name: "OpenStart", - pos: position{line: 929, col: 1, offset: 28281}, + pos: position{line: 929, col: 1, offset: 28279}, expr: &actionExpr{ - pos: position{line: 929, col: 13, offset: 28293}, + pos: position{line: 929, col: 13, offset: 28291}, run: (*parser).callonOpenStart1, expr: &seqExpr{ - pos: position{line: 929, col: 13, offset: 28293}, + pos: position{line: 929, col: 13, offset: 28291}, exprs: []any{ &labeledExpr{ - pos: position{line: 929, col: 13, offset: 28293}, + pos: position{line: 929, col: 13, offset: 28291}, label: "delimiter", expr: &ruleRefExpr{ - pos: position{line: 929, col: 23, offset: 28303}, + pos: position{line: 929, col: 23, offset: 28301}, offset: 161, }, }, &ruleRefExpr{ - pos: position{line: 929, col: 37, offset: 28317}, + pos: position{line: 929, col: 37, offset: 28315}, offset: 299, }, &andCodeExpr{ - pos: position{line: 929, col: 47, offset: 28327}, + pos: position{line: 929, col: 47, offset: 28325}, run: (*parser).callonOpenStart6, }, }, @@ -6681,27 +6681,27 @@ var g = &grammar{ }, { name: "OpenEnd", - pos: position{line: 936, col: 1, offset: 28501}, + pos: position{line: 936, col: 1, offset: 28499}, expr: &seqExpr{ - pos: position{line: 936, col: 11, offset: 28511}, + pos: position{line: 936, col: 11, offset: 28509}, exprs: []any{ &labeledExpr{ - pos: position{line: 936, col: 11, offset: 28511}, + pos: position{line: 936, col: 11, offset: 28509}, label: "delimiter", expr: &ruleRefExpr{ - pos: position{line: 936, col: 21, offset: 28521}, + pos: position{line: 936, col: 21, offset: 28519}, offset: 161, }, }, &andExpr{ - pos: position{line: 936, col: 35, offset: 28535}, + pos: position{line: 936, col: 35, offset: 28533}, expr: &ruleRefExpr{ - pos: position{line: 936, col: 36, offset: 28536}, + pos: position{line: 936, col: 36, offset: 28534}, offset: 299, }, }, &andCodeExpr{ - pos: position{line: 936, col: 46, offset: 28546}, + pos: position{line: 936, col: 46, offset: 28544}, run: (*parser).callonOpenEnd6, }, }, @@ -6709,15 +6709,15 @@ var g = &grammar{ }, { name: "OpenEquals", - pos: position{line: 945, col: 1, offset: 28800}, + pos: position{line: 945, col: 1, offset: 28798}, expr: &actionExpr{ - pos: position{line: 945, col: 14, offset: 28813}, + pos: position{line: 945, col: 14, offset: 28811}, run: (*parser).callonOpenEquals1, expr: &labeledExpr{ - pos: position{line: 945, col: 14, offset: 28813}, + pos: position{line: 945, col: 14, offset: 28811}, label: "hyphens", expr: &litMatcher{ - pos: position{line: 945, col: 23, offset: 28822}, + pos: position{line: 945, col: 23, offset: 28820}, val: "--", ignoreCase: false, want: "\"--\"", @@ -6727,23 +6727,23 @@ var g = &grammar{ }, { name: "OpenDelimiter", - pos: position{line: 950, col: 1, offset: 28878}, + pos: position{line: 950, col: 1, offset: 28876}, expr: &actionExpr{ - pos: position{line: 950, col: 17, offset: 28894}, + pos: position{line: 950, col: 17, offset: 28892}, run: (*parser).callonOpenDelimiter1, expr: &seqExpr{ - pos: position{line: 950, col: 17, offset: 28894}, + pos: position{line: 950, col: 17, offset: 28892}, exprs: []any{ &labeledExpr{ - pos: position{line: 950, col: 17, offset: 28894}, + pos: position{line: 950, col: 17, offset: 28892}, label: "hyphens", expr: &ruleRefExpr{ - pos: position{line: 950, col: 26, offset: 28903}, + pos: position{line: 950, col: 26, offset: 28901}, offset: 160, }, }, &ruleRefExpr{ - pos: position{line: 951, col: 5, offset: 28920}, + pos: position{line: 951, col: 5, offset: 28918}, offset: 303, }, }, @@ -6752,16 +6752,16 @@ var g = &grammar{ }, { name: "MarkedText", - pos: position{line: 957, col: 1, offset: 29053}, + pos: position{line: 957, col: 1, offset: 29051}, expr: &choiceExpr{ - pos: position{line: 957, col: 15, offset: 29067}, + pos: position{line: 957, col: 15, offset: 29065}, alternatives: []any{ &ruleRefExpr{ - pos: position{line: 957, col: 15, offset: 29067}, + pos: position{line: 957, col: 15, offset: 29065}, offset: 163, }, &ruleRefExpr{ - pos: position{line: 957, col: 34, offset: 29086}, + pos: position{line: 957, col: 34, offset: 29084}, offset: 166, }, }, @@ -6769,32 +6769,32 @@ var g = &grammar{ }, { name: "SingleMarkedText", - pos: position{line: 959, col: 1, offset: 29105}, + pos: position{line: 959, col: 1, offset: 29103}, expr: &actionExpr{ - pos: position{line: 959, col: 20, offset: 29124}, + pos: position{line: 959, col: 20, offset: 29122}, run: (*parser).callonSingleMarkedText1, expr: &seqExpr{ - pos: position{line: 959, col: 20, offset: 29124}, + pos: position{line: 959, col: 20, offset: 29122}, exprs: []any{ ¬Expr{ - pos: position{line: 959, col: 20, offset: 29124}, + pos: position{line: 959, col: 20, offset: 29122}, expr: &ruleRefExpr{ - pos: position{line: 959, col: 21, offset: 29125}, + pos: position{line: 959, col: 21, offset: 29123}, offset: 309, }, }, &litMatcher{ - pos: position{line: 959, col: 28, offset: 29132}, + pos: position{line: 959, col: 28, offset: 29130}, val: "#", ignoreCase: false, want: "\"#\"", }, &andExpr{ - pos: position{line: 959, col: 32, offset: 29136}, + pos: position{line: 959, col: 32, offset: 29134}, expr: ¬Expr{ - pos: position{line: 959, col: 34, offset: 29138}, + pos: position{line: 959, col: 34, offset: 29136}, expr: &litMatcher{ - pos: position{line: 959, col: 35, offset: 29139}, + pos: position{line: 959, col: 35, offset: 29137}, val: "#", ignoreCase: false, want: "\"#\"", @@ -6802,34 +6802,34 @@ var g = &grammar{ }, }, &labeledExpr{ - pos: position{line: 959, col: 40, offset: 29144}, + pos: position{line: 959, col: 40, offset: 29142}, label: "value", expr: &ruleRefExpr{ - pos: position{line: 959, col: 47, offset: 29151}, + pos: position{line: 959, col: 47, offset: 29149}, offset: 164, }, }, &litMatcher{ - pos: position{line: 959, col: 70, offset: 29174}, + pos: position{line: 959, col: 70, offset: 29172}, val: "#", ignoreCase: false, want: "\"#\"", }, ¬Expr{ - pos: position{line: 959, col: 74, offset: 29178}, + pos: position{line: 959, col: 74, offset: 29176}, expr: &litMatcher{ - pos: position{line: 959, col: 75, offset: 29179}, + pos: position{line: 959, col: 75, offset: 29177}, val: "#", ignoreCase: false, want: "\"#\"", }, }, &andExpr{ - pos: position{line: 959, col: 79, offset: 29183}, + pos: position{line: 959, col: 79, offset: 29181}, expr: ¬Expr{ - pos: position{line: 959, col: 81, offset: 29185}, + pos: position{line: 959, col: 81, offset: 29183}, expr: &ruleRefExpr{ - pos: position{line: 959, col: 82, offset: 29186}, + pos: position{line: 959, col: 82, offset: 29184}, offset: 293, }, }, @@ -6840,45 +6840,45 @@ var g = &grammar{ }, { name: "SingleMarkedTextValue", - pos: position{line: 963, col: 1, offset: 29231}, + pos: position{line: 963, col: 1, offset: 29229}, expr: &actionExpr{ - pos: position{line: 963, col: 25, offset: 29255}, + pos: position{line: 963, col: 25, offset: 29253}, run: (*parser).callonSingleMarkedTextValue1, expr: &seqExpr{ - pos: position{line: 963, col: 25, offset: 29255}, + pos: position{line: 963, col: 25, offset: 29253}, exprs: []any{ ¬Expr{ - pos: position{line: 963, col: 25, offset: 29255}, + pos: position{line: 963, col: 25, offset: 29253}, expr: &ruleRefExpr{ - pos: position{line: 963, col: 26, offset: 29256}, + pos: position{line: 963, col: 26, offset: 29254}, offset: 308, }, }, &labeledExpr{ - pos: position{line: 963, col: 28, offset: 29258}, + pos: position{line: 963, col: 28, offset: 29256}, label: "value", expr: &oneOrMoreExpr{ - pos: position{line: 963, col: 34, offset: 29264}, + pos: position{line: 963, col: 34, offset: 29262}, expr: &actionExpr{ - pos: position{line: 963, col: 35, offset: 29265}, + pos: position{line: 963, col: 35, offset: 29263}, run: (*parser).callonSingleMarkedTextValue7, expr: &seqExpr{ - pos: position{line: 963, col: 35, offset: 29265}, + pos: position{line: 963, col: 35, offset: 29263}, exprs: []any{ ¬Expr{ - pos: position{line: 963, col: 35, offset: 29265}, + pos: position{line: 963, col: 35, offset: 29263}, expr: &ruleRefExpr{ - pos: position{line: 963, col: 36, offset: 29266}, + pos: position{line: 963, col: 36, offset: 29264}, offset: 308, }, }, &labeledExpr{ - pos: position{line: 963, col: 38, offset: 29268}, + pos: position{line: 963, col: 38, offset: 29266}, label: "value", expr: &oneOrMoreExpr{ - pos: position{line: 963, col: 44, offset: 29274}, + pos: position{line: 963, col: 44, offset: 29272}, expr: &ruleRefExpr{ - pos: position{line: 963, col: 45, offset: 29275}, + pos: position{line: 963, col: 45, offset: 29273}, offset: 165, }, }, @@ -6894,25 +6894,25 @@ var g = &grammar{ }, { name: "SingleMarkedTextElement", - pos: position{line: 967, col: 1, offset: 29429}, + pos: position{line: 967, col: 1, offset: 29427}, expr: &seqExpr{ - pos: position{line: 967, col: 27, offset: 29455}, + pos: position{line: 967, col: 27, offset: 29453}, exprs: []any{ ¬Expr{ - pos: position{line: 967, col: 27, offset: 29455}, + pos: position{line: 967, col: 27, offset: 29453}, expr: &seqExpr{ - pos: position{line: 967, col: 29, offset: 29457}, + pos: position{line: 967, col: 29, offset: 29455}, exprs: []any{ &litMatcher{ - pos: position{line: 967, col: 29, offset: 29457}, + pos: position{line: 967, col: 29, offset: 29455}, val: "#", ignoreCase: false, want: "\"#\"", }, ¬Expr{ - pos: position{line: 967, col: 33, offset: 29461}, + pos: position{line: 967, col: 33, offset: 29459}, expr: &litMatcher{ - pos: position{line: 967, col: 34, offset: 29462}, + pos: position{line: 967, col: 34, offset: 29460}, val: "#", ignoreCase: false, want: "\"#\"", @@ -6922,7 +6922,7 @@ var g = &grammar{ }, }, &ruleRefExpr{ - pos: position{line: 967, col: 39, offset: 29467}, + pos: position{line: 967, col: 39, offset: 29465}, offset: 83, }, }, @@ -6930,36 +6930,36 @@ var g = &grammar{ }, { name: "DoubleMarkedText", - pos: position{line: 969, col: 1, offset: 29489}, + pos: position{line: 969, col: 1, offset: 29487}, expr: &actionExpr{ - pos: position{line: 969, col: 20, offset: 29508}, + pos: position{line: 969, col: 20, offset: 29506}, run: (*parser).callonDoubleMarkedText1, expr: &seqExpr{ - pos: position{line: 969, col: 20, offset: 29508}, + pos: position{line: 969, col: 20, offset: 29506}, exprs: []any{ ¬Expr{ - pos: position{line: 969, col: 20, offset: 29508}, + pos: position{line: 969, col: 20, offset: 29506}, expr: &ruleRefExpr{ - pos: position{line: 969, col: 21, offset: 29509}, + pos: position{line: 969, col: 21, offset: 29507}, offset: 310, }, }, &litMatcher{ - pos: position{line: 969, col: 34, offset: 29522}, + pos: position{line: 969, col: 34, offset: 29520}, val: "##", ignoreCase: false, want: "\"##\"", }, &labeledExpr{ - pos: position{line: 969, col: 39, offset: 29527}, + pos: position{line: 969, col: 39, offset: 29525}, label: "value", expr: &ruleRefExpr{ - pos: position{line: 969, col: 46, offset: 29534}, + pos: position{line: 969, col: 46, offset: 29532}, offset: 167, }, }, &litMatcher{ - pos: position{line: 969, col: 69, offset: 29557}, + pos: position{line: 969, col: 69, offset: 29555}, val: "##", ignoreCase: false, want: "\"##\"", @@ -6970,45 +6970,45 @@ var g = &grammar{ }, { name: "DoubleMarkedTextValue", - pos: position{line: 973, col: 1, offset: 29594}, + pos: position{line: 973, col: 1, offset: 29592}, expr: &actionExpr{ - pos: position{line: 973, col: 25, offset: 29618}, + pos: position{line: 973, col: 25, offset: 29616}, run: (*parser).callonDoubleMarkedTextValue1, expr: &seqExpr{ - pos: position{line: 973, col: 25, offset: 29618}, + pos: position{line: 973, col: 25, offset: 29616}, exprs: []any{ ¬Expr{ - pos: position{line: 973, col: 25, offset: 29618}, + pos: position{line: 973, col: 25, offset: 29616}, expr: &ruleRefExpr{ - pos: position{line: 973, col: 26, offset: 29619}, + pos: position{line: 973, col: 26, offset: 29617}, offset: 308, }, }, &labeledExpr{ - pos: position{line: 973, col: 28, offset: 29621}, + pos: position{line: 973, col: 28, offset: 29619}, label: "value", expr: &oneOrMoreExpr{ - pos: position{line: 973, col: 34, offset: 29627}, + pos: position{line: 973, col: 34, offset: 29625}, expr: &actionExpr{ - pos: position{line: 973, col: 35, offset: 29628}, + pos: position{line: 973, col: 35, offset: 29626}, run: (*parser).callonDoubleMarkedTextValue7, expr: &seqExpr{ - pos: position{line: 973, col: 35, offset: 29628}, + pos: position{line: 973, col: 35, offset: 29626}, exprs: []any{ ¬Expr{ - pos: position{line: 973, col: 35, offset: 29628}, + pos: position{line: 973, col: 35, offset: 29626}, expr: &ruleRefExpr{ - pos: position{line: 973, col: 36, offset: 29629}, + pos: position{line: 973, col: 36, offset: 29627}, offset: 308, }, }, &labeledExpr{ - pos: position{line: 973, col: 38, offset: 29631}, + pos: position{line: 973, col: 38, offset: 29629}, label: "value", expr: &oneOrMoreExpr{ - pos: position{line: 973, col: 44, offset: 29637}, + pos: position{line: 973, col: 44, offset: 29635}, expr: &ruleRefExpr{ - pos: position{line: 973, col: 45, offset: 29638}, + pos: position{line: 973, col: 45, offset: 29636}, offset: 168, }, }, @@ -7024,21 +7024,21 @@ var g = &grammar{ }, { name: "DoubleMarkedTextElement", - pos: position{line: 977, col: 1, offset: 29804}, + pos: position{line: 977, col: 1, offset: 29802}, expr: &seqExpr{ - pos: position{line: 977, col: 27, offset: 29830}, + pos: position{line: 977, col: 27, offset: 29828}, exprs: []any{ ¬Expr{ - pos: position{line: 977, col: 27, offset: 29830}, + pos: position{line: 977, col: 27, offset: 29828}, expr: &litMatcher{ - pos: position{line: 977, col: 29, offset: 29832}, + pos: position{line: 977, col: 29, offset: 29830}, val: "##", ignoreCase: false, want: "\"##\"", }, }, &ruleRefExpr{ - pos: position{line: 977, col: 35, offset: 29838}, + pos: position{line: 977, col: 35, offset: 29836}, offset: 83, }, }, @@ -7046,38 +7046,38 @@ var g = &grammar{ }, { name: "Example", - pos: position{line: 980, col: 1, offset: 29861}, + pos: position{line: 980, col: 1, offset: 29859}, expr: &actionExpr{ - pos: position{line: 981, col: 5, offset: 29875}, + pos: position{line: 981, col: 5, offset: 29873}, run: (*parser).callonExample1, expr: &seqExpr{ - pos: position{line: 981, col: 5, offset: 29875}, + pos: position{line: 981, col: 5, offset: 29873}, exprs: []any{ &labeledExpr{ - pos: position{line: 981, col: 5, offset: 29875}, + pos: position{line: 981, col: 5, offset: 29873}, label: "start", expr: &ruleRefExpr{ - pos: position{line: 981, col: 12, offset: 29882}, + pos: position{line: 981, col: 12, offset: 29880}, offset: 170, }, }, &labeledExpr{ - pos: position{line: 982, col: 5, offset: 29900}, + pos: position{line: 982, col: 5, offset: 29898}, label: "lines", expr: &zeroOrMoreExpr{ - pos: position{line: 982, col: 11, offset: 29906}, + pos: position{line: 982, col: 11, offset: 29904}, expr: &seqExpr{ - pos: position{line: 982, col: 12, offset: 29907}, + pos: position{line: 982, col: 12, offset: 29905}, exprs: []any{ ¬Expr{ - pos: position{line: 982, col: 12, offset: 29907}, + pos: position{line: 982, col: 12, offset: 29905}, expr: &ruleRefExpr{ - pos: position{line: 982, col: 13, offset: 29908}, + pos: position{line: 982, col: 13, offset: 29906}, offset: 173, }, }, &ruleRefExpr{ - pos: position{line: 982, col: 30, offset: 29925}, + pos: position{line: 982, col: 30, offset: 29923}, offset: 1, }, }, @@ -7085,7 +7085,7 @@ var g = &grammar{ }, }, &ruleRefExpr{ - pos: position{line: 983, col: 5, offset: 29939}, + pos: position{line: 983, col: 5, offset: 29937}, offset: 171, }, }, @@ -7094,27 +7094,27 @@ var g = &grammar{ }, { name: "ExampleStart", - pos: position{line: 987, col: 1, offset: 30097}, + pos: position{line: 987, col: 1, offset: 30095}, expr: &actionExpr{ - pos: position{line: 987, col: 16, offset: 30112}, + pos: position{line: 987, col: 16, offset: 30110}, run: (*parser).callonExampleStart1, expr: &seqExpr{ - pos: position{line: 987, col: 16, offset: 30112}, + pos: position{line: 987, col: 16, offset: 30110}, exprs: []any{ &labeledExpr{ - pos: position{line: 987, col: 16, offset: 30112}, + pos: position{line: 987, col: 16, offset: 30110}, label: "delimiter", expr: &ruleRefExpr{ - pos: position{line: 987, col: 26, offset: 30122}, + pos: position{line: 987, col: 26, offset: 30120}, offset: 173, }, }, &ruleRefExpr{ - pos: position{line: 987, col: 43, offset: 30139}, + pos: position{line: 987, col: 43, offset: 30137}, offset: 299, }, &andCodeExpr{ - pos: position{line: 987, col: 53, offset: 30149}, + pos: position{line: 987, col: 53, offset: 30147}, run: (*parser).callonExampleStart6, }, }, @@ -7123,27 +7123,27 @@ var g = &grammar{ }, { name: "ExampleEnd", - pos: position{line: 994, col: 1, offset: 30334}, + pos: position{line: 994, col: 1, offset: 30332}, expr: &seqExpr{ - pos: position{line: 994, col: 14, offset: 30347}, + pos: position{line: 994, col: 14, offset: 30345}, exprs: []any{ &labeledExpr{ - pos: position{line: 994, col: 14, offset: 30347}, + pos: position{line: 994, col: 14, offset: 30345}, label: "delimiter", expr: &ruleRefExpr{ - pos: position{line: 994, col: 24, offset: 30357}, + pos: position{line: 994, col: 24, offset: 30355}, offset: 173, }, }, &andExpr{ - pos: position{line: 994, col: 41, offset: 30374}, + pos: position{line: 994, col: 41, offset: 30372}, expr: &ruleRefExpr{ - pos: position{line: 994, col: 42, offset: 30375}, + pos: position{line: 994, col: 42, offset: 30373}, offset: 299, }, }, &andCodeExpr{ - pos: position{line: 994, col: 52, offset: 30385}, + pos: position{line: 994, col: 52, offset: 30383}, run: (*parser).callonExampleEnd6, }, }, @@ -7151,29 +7151,29 @@ var g = &grammar{ }, { name: "ExampleEquals", - pos: position{line: 1003, col: 1, offset: 30653}, + pos: position{line: 1003, col: 1, offset: 30651}, expr: &actionExpr{ - pos: position{line: 1003, col: 17, offset: 30669}, + pos: position{line: 1003, col: 17, offset: 30667}, run: (*parser).callonExampleEquals1, expr: &seqExpr{ - pos: position{line: 1003, col: 17, offset: 30669}, + pos: position{line: 1003, col: 17, offset: 30667}, exprs: []any{ &labeledExpr{ - pos: position{line: 1003, col: 17, offset: 30669}, + pos: position{line: 1003, col: 17, offset: 30667}, label: "hyphens", expr: &seqExpr{ - pos: position{line: 1003, col: 26, offset: 30678}, + pos: position{line: 1003, col: 26, offset: 30676}, exprs: []any{ &litMatcher{ - pos: position{line: 1003, col: 26, offset: 30678}, + pos: position{line: 1003, col: 26, offset: 30676}, val: "====", ignoreCase: false, want: "\"====\"", }, &zeroOrMoreExpr{ - pos: position{line: 1003, col: 33, offset: 30685}, + pos: position{line: 1003, col: 33, offset: 30683}, expr: &litMatcher{ - pos: position{line: 1003, col: 33, offset: 30685}, + pos: position{line: 1003, col: 33, offset: 30683}, val: "=", ignoreCase: false, want: "\"=\"", @@ -7183,7 +7183,7 @@ var g = &grammar{ }, }, &andCodeExpr{ - pos: position{line: 1003, col: 39, offset: 30691}, + pos: position{line: 1003, col: 39, offset: 30689}, run: (*parser).callonExampleEquals8, }, }, @@ -7192,23 +7192,23 @@ var g = &grammar{ }, { name: "ExampleDelimiter", - pos: position{line: 1010, col: 1, offset: 30814}, + pos: position{line: 1010, col: 1, offset: 30812}, expr: &actionExpr{ - pos: position{line: 1010, col: 20, offset: 30833}, + pos: position{line: 1010, col: 20, offset: 30831}, run: (*parser).callonExampleDelimiter1, expr: &seqExpr{ - pos: position{line: 1010, col: 20, offset: 30833}, + pos: position{line: 1010, col: 20, offset: 30831}, exprs: []any{ &labeledExpr{ - pos: position{line: 1010, col: 20, offset: 30833}, + pos: position{line: 1010, col: 20, offset: 30831}, label: "hyphens", expr: &ruleRefExpr{ - pos: position{line: 1010, col: 29, offset: 30842}, + pos: position{line: 1010, col: 29, offset: 30840}, offset: 172, }, }, &ruleRefExpr{ - pos: position{line: 1011, col: 5, offset: 30862}, + pos: position{line: 1011, col: 5, offset: 30860}, offset: 303, }, }, @@ -7217,34 +7217,34 @@ var g = &grammar{ }, { name: "ExampleLine", - pos: position{line: 1016, col: 1, offset: 30993}, + pos: position{line: 1016, col: 1, offset: 30991}, expr: &actionExpr{ - pos: position{line: 1016, col: 15, offset: 31007}, + pos: position{line: 1016, col: 15, offset: 31005}, run: (*parser).callonExampleLine1, expr: &seqExpr{ - pos: position{line: 1016, col: 16, offset: 31008}, + pos: position{line: 1016, col: 16, offset: 31006}, exprs: []any{ ¬Expr{ - pos: position{line: 1016, col: 16, offset: 31008}, + pos: position{line: 1016, col: 16, offset: 31006}, expr: &ruleRefExpr{ - pos: position{line: 1016, col: 17, offset: 31009}, + pos: position{line: 1016, col: 17, offset: 31007}, offset: 173, }, }, &ruleRefExpr{ - pos: position{line: 1016, col: 34, offset: 31026}, + pos: position{line: 1016, col: 34, offset: 31024}, offset: 295, }, &labeledExpr{ - pos: position{line: 1016, col: 47, offset: 31039}, + pos: position{line: 1016, col: 47, offset: 31037}, label: "line", expr: &ruleRefExpr{ - pos: position{line: 1016, col: 52, offset: 31044}, + pos: position{line: 1016, col: 52, offset: 31042}, offset: 302, }, }, &ruleRefExpr{ - pos: position{line: 1016, col: 60, offset: 31052}, + pos: position{line: 1016, col: 60, offset: 31050}, offset: 299, }, }, @@ -7253,34 +7253,34 @@ var g = &grammar{ }, { name: "Literal", - pos: position{line: 1022, col: 1, offset: 31101}, + pos: position{line: 1022, col: 1, offset: 31099}, expr: &actionExpr{ - pos: position{line: 1023, col: 5, offset: 31115}, + pos: position{line: 1023, col: 5, offset: 31113}, run: (*parser).callonLiteral1, expr: &seqExpr{ - pos: position{line: 1023, col: 5, offset: 31115}, + pos: position{line: 1023, col: 5, offset: 31113}, exprs: []any{ &labeledExpr{ - pos: position{line: 1023, col: 5, offset: 31115}, + pos: position{line: 1023, col: 5, offset: 31113}, label: "start", expr: &ruleRefExpr{ - pos: position{line: 1023, col: 12, offset: 31122}, + pos: position{line: 1023, col: 12, offset: 31120}, offset: 176, }, }, &labeledExpr{ - pos: position{line: 1024, col: 5, offset: 31140}, + pos: position{line: 1024, col: 5, offset: 31138}, label: "lines", expr: &zeroOrMoreExpr{ - pos: position{line: 1024, col: 11, offset: 31146}, + pos: position{line: 1024, col: 11, offset: 31144}, expr: &ruleRefExpr{ - pos: position{line: 1024, col: 12, offset: 31147}, + pos: position{line: 1024, col: 12, offset: 31145}, offset: 180, }, }, }, &ruleRefExpr{ - pos: position{line: 1025, col: 5, offset: 31165}, + pos: position{line: 1025, col: 5, offset: 31163}, offset: 177, }, }, @@ -7289,31 +7289,31 @@ var g = &grammar{ }, { name: "LiteralStart", - pos: position{line: 1029, col: 1, offset: 31332}, + pos: position{line: 1029, col: 1, offset: 31330}, expr: &actionExpr{ - pos: position{line: 1029, col: 16, offset: 31347}, + pos: position{line: 1029, col: 16, offset: 31345}, run: (*parser).callonLiteralStart1, expr: &seqExpr{ - pos: position{line: 1029, col: 16, offset: 31347}, + pos: position{line: 1029, col: 16, offset: 31345}, exprs: []any{ &ruleRefExpr{ - pos: position{line: 1029, col: 16, offset: 31347}, + pos: position{line: 1029, col: 16, offset: 31345}, offset: 296, }, &labeledExpr{ - pos: position{line: 1029, col: 32, offset: 31363}, + pos: position{line: 1029, col: 32, offset: 31361}, label: "delimiter", expr: &ruleRefExpr{ - pos: position{line: 1029, col: 42, offset: 31373}, + pos: position{line: 1029, col: 42, offset: 31371}, offset: 179, }, }, &ruleRefExpr{ - pos: position{line: 1029, col: 59, offset: 31390}, + pos: position{line: 1029, col: 59, offset: 31388}, offset: 299, }, &andCodeExpr{ - pos: position{line: 1029, col: 69, offset: 31400}, + pos: position{line: 1029, col: 69, offset: 31398}, run: (*parser).callonLiteralStart7, }, }, @@ -7322,31 +7322,31 @@ var g = &grammar{ }, { name: "LiteralEnd", - pos: position{line: 1036, col: 1, offset: 31577}, + pos: position{line: 1036, col: 1, offset: 31575}, expr: &seqExpr{ - pos: position{line: 1036, col: 14, offset: 31590}, + pos: position{line: 1036, col: 14, offset: 31588}, exprs: []any{ &ruleRefExpr{ - pos: position{line: 1036, col: 14, offset: 31590}, + pos: position{line: 1036, col: 14, offset: 31588}, offset: 296, }, &labeledExpr{ - pos: position{line: 1036, col: 30, offset: 31606}, + pos: position{line: 1036, col: 30, offset: 31604}, label: "delimiter", expr: &ruleRefExpr{ - pos: position{line: 1036, col: 40, offset: 31616}, + pos: position{line: 1036, col: 40, offset: 31614}, offset: 179, }, }, &andExpr{ - pos: position{line: 1036, col: 57, offset: 31633}, + pos: position{line: 1036, col: 57, offset: 31631}, expr: &ruleRefExpr{ - pos: position{line: 1036, col: 58, offset: 31634}, + pos: position{line: 1036, col: 58, offset: 31632}, offset: 299, }, }, &andCodeExpr{ - pos: position{line: 1036, col: 68, offset: 31644}, + pos: position{line: 1036, col: 68, offset: 31642}, run: (*parser).callonLiteralEnd7, }, }, @@ -7354,29 +7354,29 @@ var g = &grammar{ }, { name: "LiteralDots", - pos: position{line: 1045, col: 1, offset: 31904}, + pos: position{line: 1045, col: 1, offset: 31902}, expr: &actionExpr{ - pos: position{line: 1045, col: 15, offset: 31918}, + pos: position{line: 1045, col: 15, offset: 31916}, run: (*parser).callonLiteralDots1, expr: &seqExpr{ - pos: position{line: 1045, col: 15, offset: 31918}, + pos: position{line: 1045, col: 15, offset: 31916}, exprs: []any{ &labeledExpr{ - pos: position{line: 1045, col: 15, offset: 31918}, + pos: position{line: 1045, col: 15, offset: 31916}, label: "dots", expr: &seqExpr{ - pos: position{line: 1045, col: 21, offset: 31924}, + pos: position{line: 1045, col: 21, offset: 31922}, exprs: []any{ &litMatcher{ - pos: position{line: 1045, col: 21, offset: 31924}, + pos: position{line: 1045, col: 21, offset: 31922}, val: "....", ignoreCase: false, want: "\"....\"", }, &zeroOrMoreExpr{ - pos: position{line: 1045, col: 28, offset: 31931}, + pos: position{line: 1045, col: 28, offset: 31929}, expr: &litMatcher{ - pos: position{line: 1045, col: 28, offset: 31931}, + pos: position{line: 1045, col: 28, offset: 31929}, val: ".", ignoreCase: false, want: "\".\"", @@ -7386,7 +7386,7 @@ var g = &grammar{ }, }, &andCodeExpr{ - pos: position{line: 1045, col: 34, offset: 31937}, + pos: position{line: 1045, col: 34, offset: 31935}, run: (*parser).callonLiteralDots8, }, }, @@ -7395,23 +7395,23 @@ var g = &grammar{ }, { name: "LiteralDelimiter", - pos: position{line: 1052, col: 1, offset: 32050}, + pos: position{line: 1052, col: 1, offset: 32048}, expr: &actionExpr{ - pos: position{line: 1052, col: 20, offset: 32069}, + pos: position{line: 1052, col: 20, offset: 32067}, run: (*parser).callonLiteralDelimiter1, expr: &seqExpr{ - pos: position{line: 1052, col: 20, offset: 32069}, + pos: position{line: 1052, col: 20, offset: 32067}, exprs: []any{ &labeledExpr{ - pos: position{line: 1052, col: 20, offset: 32069}, + pos: position{line: 1052, col: 20, offset: 32067}, label: "dots", expr: &ruleRefExpr{ - pos: position{line: 1052, col: 26, offset: 32075}, + pos: position{line: 1052, col: 26, offset: 32073}, offset: 178, }, }, &ruleRefExpr{ - pos: position{line: 1053, col: 5, offset: 32093}, + pos: position{line: 1053, col: 5, offset: 32091}, offset: 303, }, }, @@ -7420,34 +7420,34 @@ var g = &grammar{ }, { name: "LiteralLine", - pos: position{line: 1058, col: 1, offset: 32217}, + pos: position{line: 1058, col: 1, offset: 32215}, expr: &actionExpr{ - pos: position{line: 1058, col: 15, offset: 32231}, + pos: position{line: 1058, col: 15, offset: 32229}, run: (*parser).callonLiteralLine1, expr: &seqExpr{ - pos: position{line: 1058, col: 16, offset: 32232}, + pos: position{line: 1058, col: 16, offset: 32230}, exprs: []any{ ¬Expr{ - pos: position{line: 1058, col: 16, offset: 32232}, + pos: position{line: 1058, col: 16, offset: 32230}, expr: &ruleRefExpr{ - pos: position{line: 1058, col: 17, offset: 32233}, + pos: position{line: 1058, col: 17, offset: 32231}, offset: 179, }, }, &ruleRefExpr{ - pos: position{line: 1058, col: 34, offset: 32250}, + pos: position{line: 1058, col: 34, offset: 32248}, offset: 295, }, &labeledExpr{ - pos: position{line: 1058, col: 47, offset: 32263}, + pos: position{line: 1058, col: 47, offset: 32261}, label: "line", expr: &ruleRefExpr{ - pos: position{line: 1058, col: 52, offset: 32268}, + pos: position{line: 1058, col: 52, offset: 32266}, offset: 302, }, }, &ruleRefExpr{ - pos: position{line: 1058, col: 60, offset: 32276}, + pos: position{line: 1058, col: 60, offset: 32274}, offset: 299, }, }, @@ -7456,38 +7456,38 @@ var g = &grammar{ }, { name: "Sidebar", - pos: position{line: 1062, col: 1, offset: 32323}, + pos: position{line: 1062, col: 1, offset: 32321}, expr: &actionExpr{ - pos: position{line: 1063, col: 5, offset: 32337}, + pos: position{line: 1063, col: 5, offset: 32335}, run: (*parser).callonSidebar1, expr: &seqExpr{ - pos: position{line: 1063, col: 5, offset: 32337}, + pos: position{line: 1063, col: 5, offset: 32335}, exprs: []any{ &labeledExpr{ - pos: position{line: 1063, col: 5, offset: 32337}, + pos: position{line: 1063, col: 5, offset: 32335}, label: "start", expr: &ruleRefExpr{ - pos: position{line: 1063, col: 12, offset: 32344}, + pos: position{line: 1063, col: 12, offset: 32342}, offset: 182, }, }, &labeledExpr{ - pos: position{line: 1064, col: 5, offset: 32362}, + pos: position{line: 1064, col: 5, offset: 32360}, label: "lines", expr: &zeroOrMoreExpr{ - pos: position{line: 1064, col: 11, offset: 32368}, + pos: position{line: 1064, col: 11, offset: 32366}, expr: &seqExpr{ - pos: position{line: 1064, col: 12, offset: 32369}, + pos: position{line: 1064, col: 12, offset: 32367}, exprs: []any{ ¬Expr{ - pos: position{line: 1064, col: 12, offset: 32369}, + pos: position{line: 1064, col: 12, offset: 32367}, expr: &ruleRefExpr{ - pos: position{line: 1064, col: 13, offset: 32370}, + pos: position{line: 1064, col: 13, offset: 32368}, offset: 185, }, }, &ruleRefExpr{ - pos: position{line: 1064, col: 30, offset: 32387}, + pos: position{line: 1064, col: 30, offset: 32385}, offset: 1, }, }, @@ -7495,7 +7495,7 @@ var g = &grammar{ }, }, &ruleRefExpr{ - pos: position{line: 1065, col: 5, offset: 32401}, + pos: position{line: 1065, col: 5, offset: 32399}, offset: 183, }, }, @@ -7504,27 +7504,27 @@ var g = &grammar{ }, { name: "SidebarStart", - pos: position{line: 1069, col: 1, offset: 32559}, + pos: position{line: 1069, col: 1, offset: 32557}, expr: &actionExpr{ - pos: position{line: 1069, col: 16, offset: 32574}, + pos: position{line: 1069, col: 16, offset: 32572}, run: (*parser).callonSidebarStart1, expr: &seqExpr{ - pos: position{line: 1069, col: 16, offset: 32574}, + pos: position{line: 1069, col: 16, offset: 32572}, exprs: []any{ &labeledExpr{ - pos: position{line: 1069, col: 16, offset: 32574}, + pos: position{line: 1069, col: 16, offset: 32572}, label: "delimiter", expr: &ruleRefExpr{ - pos: position{line: 1069, col: 26, offset: 32584}, + pos: position{line: 1069, col: 26, offset: 32582}, offset: 185, }, }, &ruleRefExpr{ - pos: position{line: 1069, col: 43, offset: 32601}, + pos: position{line: 1069, col: 43, offset: 32599}, offset: 299, }, &andCodeExpr{ - pos: position{line: 1069, col: 53, offset: 32611}, + pos: position{line: 1069, col: 53, offset: 32609}, run: (*parser).callonSidebarStart6, }, }, @@ -7533,27 +7533,27 @@ var g = &grammar{ }, { name: "SidebarEnd", - pos: position{line: 1076, col: 1, offset: 32788}, + pos: position{line: 1076, col: 1, offset: 32786}, expr: &seqExpr{ - pos: position{line: 1076, col: 14, offset: 32801}, + pos: position{line: 1076, col: 14, offset: 32799}, exprs: []any{ &labeledExpr{ - pos: position{line: 1076, col: 14, offset: 32801}, + pos: position{line: 1076, col: 14, offset: 32799}, label: "delimiter", expr: &ruleRefExpr{ - pos: position{line: 1076, col: 24, offset: 32811}, + pos: position{line: 1076, col: 24, offset: 32809}, offset: 185, }, }, &andExpr{ - pos: position{line: 1076, col: 41, offset: 32828}, + pos: position{line: 1076, col: 41, offset: 32826}, expr: &ruleRefExpr{ - pos: position{line: 1076, col: 42, offset: 32829}, + pos: position{line: 1076, col: 42, offset: 32827}, offset: 299, }, }, &andCodeExpr{ - pos: position{line: 1076, col: 52, offset: 32839}, + pos: position{line: 1076, col: 52, offset: 32837}, run: (*parser).callonSidebarEnd6, }, }, @@ -7561,29 +7561,29 @@ var g = &grammar{ }, { name: "SidebarAsterisks", - pos: position{line: 1085, col: 1, offset: 33099}, + pos: position{line: 1085, col: 1, offset: 33097}, expr: &actionExpr{ - pos: position{line: 1085, col: 20, offset: 33118}, + pos: position{line: 1085, col: 20, offset: 33116}, run: (*parser).callonSidebarAsterisks1, expr: &seqExpr{ - pos: position{line: 1085, col: 20, offset: 33118}, + pos: position{line: 1085, col: 20, offset: 33116}, exprs: []any{ &labeledExpr{ - pos: position{line: 1085, col: 20, offset: 33118}, + pos: position{line: 1085, col: 20, offset: 33116}, label: "asterisks", expr: &seqExpr{ - pos: position{line: 1085, col: 31, offset: 33129}, + pos: position{line: 1085, col: 31, offset: 33127}, exprs: []any{ &litMatcher{ - pos: position{line: 1085, col: 31, offset: 33129}, + pos: position{line: 1085, col: 31, offset: 33127}, val: "****", ignoreCase: false, want: "\"****\"", }, &zeroOrMoreExpr{ - pos: position{line: 1085, col: 38, offset: 33136}, + pos: position{line: 1085, col: 38, offset: 33134}, expr: &litMatcher{ - pos: position{line: 1085, col: 38, offset: 33136}, + pos: position{line: 1085, col: 38, offset: 33134}, val: "*", ignoreCase: false, want: "\"*\"", @@ -7593,7 +7593,7 @@ var g = &grammar{ }, }, &andCodeExpr{ - pos: position{line: 1085, col: 44, offset: 33142}, + pos: position{line: 1085, col: 44, offset: 33140}, run: (*parser).callonSidebarAsterisks8, }, }, @@ -7602,23 +7602,23 @@ var g = &grammar{ }, { name: "SidebarDelimiter", - pos: position{line: 1092, col: 1, offset: 33265}, + pos: position{line: 1092, col: 1, offset: 33263}, expr: &actionExpr{ - pos: position{line: 1092, col: 20, offset: 33284}, + pos: position{line: 1092, col: 20, offset: 33282}, run: (*parser).callonSidebarDelimiter1, expr: &seqExpr{ - pos: position{line: 1092, col: 20, offset: 33284}, + pos: position{line: 1092, col: 20, offset: 33282}, exprs: []any{ &labeledExpr{ - pos: position{line: 1092, col: 20, offset: 33284}, + pos: position{line: 1092, col: 20, offset: 33282}, label: "asterisks", expr: &ruleRefExpr{ - pos: position{line: 1092, col: 31, offset: 33295}, + pos: position{line: 1092, col: 31, offset: 33293}, offset: 184, }, }, &ruleRefExpr{ - pos: position{line: 1093, col: 5, offset: 33318}, + pos: position{line: 1093, col: 5, offset: 33316}, offset: 303, }, }, @@ -7627,26 +7627,26 @@ var g = &grammar{ }, { name: "PreParse", - pos: position{line: 1099, col: 1, offset: 33448}, + pos: position{line: 1099, col: 1, offset: 33446}, expr: &actionExpr{ - pos: position{line: 1099, col: 12, offset: 33459}, + pos: position{line: 1099, col: 12, offset: 33457}, run: (*parser).callonPreParse1, expr: &seqExpr{ - pos: position{line: 1099, col: 12, offset: 33459}, + pos: position{line: 1099, col: 12, offset: 33457}, exprs: []any{ &labeledExpr{ - pos: position{line: 1099, col: 12, offset: 33459}, + pos: position{line: 1099, col: 12, offset: 33457}, label: "content", expr: &oneOrMoreExpr{ - pos: position{line: 1099, col: 20, offset: 33467}, + pos: position{line: 1099, col: 20, offset: 33465}, expr: &ruleRefExpr{ - pos: position{line: 1099, col: 21, offset: 33468}, + pos: position{line: 1099, col: 21, offset: 33466}, offset: 187, }, }, }, &ruleRefExpr{ - pos: position{line: 1099, col: 39, offset: 33486}, + pos: position{line: 1099, col: 39, offset: 33484}, offset: 294, }, }, @@ -7655,63 +7655,63 @@ var g = &grammar{ }, { name: "PreParseElement", - pos: position{line: 1103, col: 1, offset: 33543}, + pos: position{line: 1103, col: 1, offset: 33541}, expr: &actionExpr{ - pos: position{line: 1103, col: 19, offset: 33561}, + pos: position{line: 1103, col: 19, offset: 33559}, run: (*parser).callonPreParseElement1, expr: &seqExpr{ - pos: position{line: 1103, col: 19, offset: 33561}, + pos: position{line: 1103, col: 19, offset: 33559}, exprs: []any{ ¬Expr{ - pos: position{line: 1103, col: 19, offset: 33561}, + pos: position{line: 1103, col: 19, offset: 33559}, expr: &ruleRefExpr{ - pos: position{line: 1103, col: 20, offset: 33562}, + pos: position{line: 1103, col: 20, offset: 33560}, offset: 294, }, }, &ruleRefExpr{ - pos: position{line: 1103, col: 30, offset: 33572}, + pos: position{line: 1103, col: 30, offset: 33570}, offset: 296, }, &labeledExpr{ - pos: position{line: 1103, col: 46, offset: 33588}, + pos: position{line: 1103, col: 46, offset: 33586}, label: "element", expr: &choiceExpr{ - pos: position{line: 1103, col: 55, offset: 33597}, + pos: position{line: 1103, col: 55, offset: 33595}, alternatives: []any{ &ruleRefExpr{ - pos: position{line: 1103, col: 55, offset: 33597}, + pos: position{line: 1103, col: 55, offset: 33595}, offset: 190, }, &ruleRefExpr{ - pos: position{line: 1103, col: 68, offset: 33610}, + pos: position{line: 1103, col: 68, offset: 33608}, offset: 191, }, &ruleRefExpr{ - pos: position{line: 1103, col: 82, offset: 33624}, + pos: position{line: 1103, col: 82, offset: 33622}, offset: 192, }, &ruleRefExpr{ - pos: position{line: 1103, col: 96, offset: 33638}, + pos: position{line: 1103, col: 96, offset: 33636}, offset: 18, }, &ruleRefExpr{ - pos: position{line: 1103, col: 113, offset: 33655}, + pos: position{line: 1103, col: 113, offset: 33653}, offset: 22, }, &ruleRefExpr{ - pos: position{line: 1103, col: 130, offset: 33672}, + pos: position{line: 1103, col: 130, offset: 33670}, offset: 89, }, &ruleRefExpr{ - pos: position{line: 1103, col: 144, offset: 33686}, + pos: position{line: 1103, col: 144, offset: 33684}, offset: 188, }, }, }, }, &ruleRefExpr{ - pos: position{line: 1103, col: 158, offset: 33700}, + pos: position{line: 1103, col: 158, offset: 33698}, offset: 299, }, }, @@ -7720,35 +7720,35 @@ var g = &grammar{ }, { name: "PreParseLine", - pos: position{line: 1107, col: 1, offset: 33739}, + pos: position{line: 1107, col: 1, offset: 33737}, expr: &actionExpr{ - pos: position{line: 1107, col: 16, offset: 33754}, + pos: position{line: 1107, col: 16, offset: 33752}, run: (*parser).callonPreParseLine1, expr: &seqExpr{ - pos: position{line: 1107, col: 16, offset: 33754}, + pos: position{line: 1107, col: 16, offset: 33752}, exprs: []any{ ¬Expr{ - pos: position{line: 1107, col: 16, offset: 33754}, + pos: position{line: 1107, col: 16, offset: 33752}, expr: &ruleRefExpr{ - pos: position{line: 1107, col: 17, offset: 33755}, + pos: position{line: 1107, col: 17, offset: 33753}, offset: 268, }, }, &labeledExpr{ - pos: position{line: 1107, col: 35, offset: 33773}, + pos: position{line: 1107, col: 35, offset: 33771}, label: "content", expr: &zeroOrMoreExpr{ - pos: position{line: 1107, col: 43, offset: 33781}, + pos: position{line: 1107, col: 43, offset: 33779}, expr: &ruleRefExpr{ - pos: position{line: 1107, col: 43, offset: 33781}, + pos: position{line: 1107, col: 43, offset: 33779}, offset: 189, }, }, }, &andExpr{ - pos: position{line: 1107, col: 64, offset: 33802}, + pos: position{line: 1107, col: 64, offset: 33800}, expr: &ruleRefExpr{ - pos: position{line: 1107, col: 65, offset: 33803}, + pos: position{line: 1107, col: 65, offset: 33801}, offset: 299, }, }, @@ -7758,18 +7758,18 @@ var g = &grammar{ }, { name: "PreParseLineElement", - pos: position{line: 1111, col: 1, offset: 33890}, + pos: position{line: 1111, col: 1, offset: 33888}, expr: &choiceExpr{ - pos: position{line: 1112, col: 4, offset: 33917}, + pos: position{line: 1112, col: 4, offset: 33915}, alternatives: []any{ &ruleRefExpr{ - pos: position{line: 1112, col: 4, offset: 33917}, + pos: position{line: 1112, col: 4, offset: 33915}, offset: 41, }, &oneOrMoreExpr{ - pos: position{line: 1114, col: 6, offset: 33948}, + pos: position{line: 1114, col: 6, offset: 33946}, expr: &charClassMatcher{ - pos: position{line: 1114, col: 6, offset: 33948}, + pos: position{line: 1114, col: 6, offset: 33946}, val: "[^\\r\\n{]", chars: []rune{'\r', '\n', '{'}, ignoreCase: false, @@ -7777,7 +7777,7 @@ var g = &grammar{ }, }, &litMatcher{ - pos: position{line: 1115, col: 7, offset: 33966}, + pos: position{line: 1115, col: 7, offset: 33964}, val: "{", ignoreCase: false, want: "\"{\"", @@ -7787,52 +7787,52 @@ var g = &grammar{ }, { name: "IfDefBlock", - pos: position{line: 1118, col: 1, offset: 33974}, + pos: position{line: 1118, col: 1, offset: 33972}, expr: &actionExpr{ - pos: position{line: 1118, col: 14, offset: 33987}, + pos: position{line: 1118, col: 14, offset: 33985}, run: (*parser).callonIfDefBlock1, expr: &seqExpr{ - pos: position{line: 1118, col: 14, offset: 33987}, + pos: position{line: 1118, col: 14, offset: 33985}, exprs: []any{ &ruleRefExpr{ - pos: position{line: 1118, col: 14, offset: 33987}, + pos: position{line: 1118, col: 14, offset: 33985}, offset: 263, }, &labeledExpr{ - pos: position{line: 1118, col: 29, offset: 34002}, + pos: position{line: 1118, col: 29, offset: 34000}, label: "attributes", expr: &ruleRefExpr{ - pos: position{line: 1118, col: 41, offset: 34014}, + pos: position{line: 1118, col: 41, offset: 34012}, offset: 269, }, }, &litMatcher{ - pos: position{line: 1118, col: 64, offset: 34037}, + pos: position{line: 1118, col: 64, offset: 34035}, val: "[]", ignoreCase: false, want: "\"[]\"", }, &ruleRefExpr{ - pos: position{line: 1118, col: 69, offset: 34042}, + pos: position{line: 1118, col: 69, offset: 34040}, offset: 303, }, &ruleRefExpr{ - pos: position{line: 1118, col: 80, offset: 34053}, + pos: position{line: 1118, col: 80, offset: 34051}, offset: 299, }, &labeledExpr{ - pos: position{line: 1118, col: 90, offset: 34063}, + pos: position{line: 1118, col: 90, offset: 34061}, label: "lines", expr: &zeroOrMoreExpr{ - pos: position{line: 1118, col: 96, offset: 34069}, + pos: position{line: 1118, col: 96, offset: 34067}, expr: &ruleRefExpr{ - pos: position{line: 1118, col: 97, offset: 34070}, + pos: position{line: 1118, col: 97, offset: 34068}, offset: 187, }, }, }, &ruleRefExpr{ - pos: position{line: 1118, col: 116, offset: 34089}, + pos: position{line: 1118, col: 116, offset: 34087}, offset: 267, }, }, @@ -7841,54 +7841,54 @@ var g = &grammar{ }, { name: "IfNDefBlock", - pos: position{line: 1123, col: 1, offset: 34277}, + pos: position{line: 1123, col: 1, offset: 34275}, expr: &actionExpr{ - pos: position{line: 1123, col: 15, offset: 34291}, + pos: position{line: 1123, col: 15, offset: 34289}, run: (*parser).callonIfNDefBlock1, expr: &seqExpr{ - pos: position{line: 1123, col: 15, offset: 34291}, + pos: position{line: 1123, col: 15, offset: 34289}, exprs: []any{ &litMatcher{ - pos: position{line: 1123, col: 15, offset: 34291}, + pos: position{line: 1123, col: 15, offset: 34289}, val: "ifndef::", ignoreCase: false, want: "\"ifndef::\"", }, &labeledExpr{ - pos: position{line: 1123, col: 26, offset: 34302}, + pos: position{line: 1123, col: 26, offset: 34300}, label: "attributes", expr: &ruleRefExpr{ - pos: position{line: 1123, col: 38, offset: 34314}, + pos: position{line: 1123, col: 38, offset: 34312}, offset: 269, }, }, &litMatcher{ - pos: position{line: 1123, col: 61, offset: 34337}, + pos: position{line: 1123, col: 61, offset: 34335}, val: "[]", ignoreCase: false, want: "\"[]\"", }, &ruleRefExpr{ - pos: position{line: 1123, col: 66, offset: 34342}, + pos: position{line: 1123, col: 66, offset: 34340}, offset: 303, }, &ruleRefExpr{ - pos: position{line: 1123, col: 77, offset: 34353}, + pos: position{line: 1123, col: 77, offset: 34351}, offset: 299, }, &labeledExpr{ - pos: position{line: 1123, col: 87, offset: 34363}, + pos: position{line: 1123, col: 87, offset: 34361}, label: "lines", expr: &zeroOrMoreExpr{ - pos: position{line: 1123, col: 93, offset: 34369}, + pos: position{line: 1123, col: 93, offset: 34367}, expr: &ruleRefExpr{ - pos: position{line: 1123, col: 94, offset: 34370}, + pos: position{line: 1123, col: 94, offset: 34368}, offset: 187, }, }, }, &ruleRefExpr{ - pos: position{line: 1123, col: 113, offset: 34389}, + pos: position{line: 1123, col: 113, offset: 34387}, offset: 267, }, }, @@ -7897,90 +7897,90 @@ var g = &grammar{ }, { name: "IfEvalBlock", - pos: position{line: 1128, col: 1, offset: 34579}, + pos: position{line: 1128, col: 1, offset: 34577}, expr: &actionExpr{ - pos: position{line: 1128, col: 15, offset: 34593}, + pos: position{line: 1128, col: 15, offset: 34591}, run: (*parser).callonIfEvalBlock1, expr: &seqExpr{ - pos: position{line: 1128, col: 15, offset: 34593}, + pos: position{line: 1128, col: 15, offset: 34591}, exprs: []any{ &ruleRefExpr{ - pos: position{line: 1128, col: 15, offset: 34593}, + pos: position{line: 1128, col: 15, offset: 34591}, offset: 274, }, &litMatcher{ - pos: position{line: 1128, col: 31, offset: 34609}, + pos: position{line: 1128, col: 31, offset: 34607}, val: "[", ignoreCase: false, want: "\"[\"", }, &ruleRefExpr{ - pos: position{line: 1128, col: 35, offset: 34613}, + pos: position{line: 1128, col: 35, offset: 34611}, offset: 303, }, &labeledExpr{ - pos: position{line: 1128, col: 45, offset: 34623}, + pos: position{line: 1128, col: 45, offset: 34621}, label: "left", expr: &ruleRefExpr{ - pos: position{line: 1128, col: 51, offset: 34629}, + pos: position{line: 1128, col: 51, offset: 34627}, offset: 275, }, }, &ruleRefExpr{ - pos: position{line: 1128, col: 64, offset: 34642}, + pos: position{line: 1128, col: 64, offset: 34640}, offset: 303, }, &labeledExpr{ - pos: position{line: 1128, col: 75, offset: 34653}, + pos: position{line: 1128, col: 75, offset: 34651}, label: "operand", expr: &ruleRefExpr{ - pos: position{line: 1128, col: 84, offset: 34662}, + pos: position{line: 1128, col: 84, offset: 34660}, offset: 276, }, }, &ruleRefExpr{ - pos: position{line: 1128, col: 100, offset: 34678}, + pos: position{line: 1128, col: 100, offset: 34676}, offset: 303, }, &labeledExpr{ - pos: position{line: 1128, col: 110, offset: 34688}, + pos: position{line: 1128, col: 110, offset: 34686}, label: "right", expr: &ruleRefExpr{ - pos: position{line: 1128, col: 117, offset: 34695}, + pos: position{line: 1128, col: 117, offset: 34693}, offset: 275, }, }, &ruleRefExpr{ - pos: position{line: 1128, col: 130, offset: 34708}, + pos: position{line: 1128, col: 130, offset: 34706}, offset: 303, }, &litMatcher{ - pos: position{line: 1128, col: 140, offset: 34718}, + pos: position{line: 1128, col: 140, offset: 34716}, val: "]", ignoreCase: false, want: "\"]\"", }, &ruleRefExpr{ - pos: position{line: 1128, col: 144, offset: 34722}, + pos: position{line: 1128, col: 144, offset: 34720}, offset: 303, }, &ruleRefExpr{ - pos: position{line: 1128, col: 155, offset: 34733}, + pos: position{line: 1128, col: 155, offset: 34731}, offset: 299, }, &labeledExpr{ - pos: position{line: 1128, col: 165, offset: 34743}, + pos: position{line: 1128, col: 165, offset: 34741}, label: "lines", expr: &zeroOrMoreExpr{ - pos: position{line: 1128, col: 171, offset: 34749}, + pos: position{line: 1128, col: 171, offset: 34747}, expr: &ruleRefExpr{ - pos: position{line: 1128, col: 172, offset: 34750}, + pos: position{line: 1128, col: 172, offset: 34748}, offset: 187, }, }, }, &ruleRefExpr{ - pos: position{line: 1128, col: 191, offset: 34769}, + pos: position{line: 1128, col: 191, offset: 34767}, offset: 267, }, }, @@ -7989,38 +7989,38 @@ var g = &grammar{ }, { name: "Quote", - pos: position{line: 1135, col: 1, offset: 34990}, + pos: position{line: 1135, col: 1, offset: 34988}, expr: &actionExpr{ - pos: position{line: 1136, col: 5, offset: 35002}, + pos: position{line: 1136, col: 5, offset: 35000}, run: (*parser).callonQuote1, expr: &seqExpr{ - pos: position{line: 1136, col: 5, offset: 35002}, + pos: position{line: 1136, col: 5, offset: 35000}, exprs: []any{ &labeledExpr{ - pos: position{line: 1136, col: 5, offset: 35002}, + pos: position{line: 1136, col: 5, offset: 35000}, label: "start", expr: &ruleRefExpr{ - pos: position{line: 1136, col: 12, offset: 35009}, + pos: position{line: 1136, col: 12, offset: 35007}, offset: 194, }, }, &labeledExpr{ - pos: position{line: 1137, col: 5, offset: 35025}, + pos: position{line: 1137, col: 5, offset: 35023}, label: "lines", expr: &zeroOrMoreExpr{ - pos: position{line: 1137, col: 11, offset: 35031}, + pos: position{line: 1137, col: 11, offset: 35029}, expr: &seqExpr{ - pos: position{line: 1137, col: 12, offset: 35032}, + pos: position{line: 1137, col: 12, offset: 35030}, exprs: []any{ ¬Expr{ - pos: position{line: 1137, col: 12, offset: 35032}, + pos: position{line: 1137, col: 12, offset: 35030}, expr: &ruleRefExpr{ - pos: position{line: 1137, col: 13, offset: 35033}, + pos: position{line: 1137, col: 13, offset: 35031}, offset: 197, }, }, &ruleRefExpr{ - pos: position{line: 1137, col: 28, offset: 35048}, + pos: position{line: 1137, col: 28, offset: 35046}, offset: 1, }, }, @@ -8028,7 +8028,7 @@ var g = &grammar{ }, }, &ruleRefExpr{ - pos: position{line: 1138, col: 5, offset: 35062}, + pos: position{line: 1138, col: 5, offset: 35060}, offset: 195, }, }, @@ -8037,27 +8037,27 @@ var g = &grammar{ }, { name: "QuoteStart", - pos: position{line: 1142, col: 1, offset: 35214}, + pos: position{line: 1142, col: 1, offset: 35212}, expr: &actionExpr{ - pos: position{line: 1142, col: 14, offset: 35227}, + pos: position{line: 1142, col: 14, offset: 35225}, run: (*parser).callonQuoteStart1, expr: &seqExpr{ - pos: position{line: 1142, col: 14, offset: 35227}, + pos: position{line: 1142, col: 14, offset: 35225}, exprs: []any{ &labeledExpr{ - pos: position{line: 1142, col: 14, offset: 35227}, + pos: position{line: 1142, col: 14, offset: 35225}, label: "delimiter", expr: &ruleRefExpr{ - pos: position{line: 1142, col: 24, offset: 35237}, + pos: position{line: 1142, col: 24, offset: 35235}, offset: 197, }, }, &ruleRefExpr{ - pos: position{line: 1142, col: 39, offset: 35252}, + pos: position{line: 1142, col: 39, offset: 35250}, offset: 299, }, &andCodeExpr{ - pos: position{line: 1142, col: 49, offset: 35262}, + pos: position{line: 1142, col: 49, offset: 35260}, run: (*parser).callonQuoteStart6, }, }, @@ -8066,27 +8066,27 @@ var g = &grammar{ }, { name: "QuoteEnd", - pos: position{line: 1149, col: 1, offset: 35437}, + pos: position{line: 1149, col: 1, offset: 35435}, expr: &seqExpr{ - pos: position{line: 1149, col: 12, offset: 35448}, + pos: position{line: 1149, col: 12, offset: 35446}, exprs: []any{ &labeledExpr{ - pos: position{line: 1149, col: 12, offset: 35448}, + pos: position{line: 1149, col: 12, offset: 35446}, label: "delimiter", expr: &ruleRefExpr{ - pos: position{line: 1149, col: 22, offset: 35458}, + pos: position{line: 1149, col: 22, offset: 35456}, offset: 197, }, }, &andExpr{ - pos: position{line: 1149, col: 37, offset: 35473}, + pos: position{line: 1149, col: 37, offset: 35471}, expr: &ruleRefExpr{ - pos: position{line: 1149, col: 38, offset: 35474}, + pos: position{line: 1149, col: 38, offset: 35472}, offset: 299, }, }, &andCodeExpr{ - pos: position{line: 1149, col: 48, offset: 35484}, + pos: position{line: 1149, col: 48, offset: 35482}, run: (*parser).callonQuoteEnd6, }, }, @@ -8094,29 +8094,29 @@ var g = &grammar{ }, { name: "QuoteUnderlines", - pos: position{line: 1158, col: 1, offset: 35736}, + pos: position{line: 1158, col: 1, offset: 35734}, expr: &actionExpr{ - pos: position{line: 1158, col: 19, offset: 35754}, + pos: position{line: 1158, col: 19, offset: 35752}, run: (*parser).callonQuoteUnderlines1, expr: &seqExpr{ - pos: position{line: 1158, col: 19, offset: 35754}, + pos: position{line: 1158, col: 19, offset: 35752}, exprs: []any{ &labeledExpr{ - pos: position{line: 1158, col: 19, offset: 35754}, + pos: position{line: 1158, col: 19, offset: 35752}, label: "underlines", expr: &seqExpr{ - pos: position{line: 1158, col: 31, offset: 35766}, + pos: position{line: 1158, col: 31, offset: 35764}, exprs: []any{ &litMatcher{ - pos: position{line: 1158, col: 31, offset: 35766}, + pos: position{line: 1158, col: 31, offset: 35764}, val: "____", ignoreCase: false, want: "\"____\"", }, &zeroOrMoreExpr{ - pos: position{line: 1158, col: 38, offset: 35773}, + pos: position{line: 1158, col: 38, offset: 35771}, expr: &litMatcher{ - pos: position{line: 1158, col: 38, offset: 35773}, + pos: position{line: 1158, col: 38, offset: 35771}, val: "_", ignoreCase: false, want: "\"_\"", @@ -8126,7 +8126,7 @@ var g = &grammar{ }, }, &andCodeExpr{ - pos: position{line: 1158, col: 44, offset: 35779}, + pos: position{line: 1158, col: 44, offset: 35777}, run: (*parser).callonQuoteUnderlines8, }, }, @@ -8135,23 +8135,23 @@ var g = &grammar{ }, { name: "QuoteDelimiter", - pos: position{line: 1165, col: 1, offset: 35904}, + pos: position{line: 1165, col: 1, offset: 35902}, expr: &actionExpr{ - pos: position{line: 1165, col: 18, offset: 35921}, + pos: position{line: 1165, col: 18, offset: 35919}, run: (*parser).callonQuoteDelimiter1, expr: &seqExpr{ - pos: position{line: 1165, col: 18, offset: 35921}, + pos: position{line: 1165, col: 18, offset: 35919}, exprs: []any{ &labeledExpr{ - pos: position{line: 1165, col: 18, offset: 35921}, + pos: position{line: 1165, col: 18, offset: 35919}, label: "underlines", expr: &ruleRefExpr{ - pos: position{line: 1165, col: 30, offset: 35933}, + pos: position{line: 1165, col: 30, offset: 35931}, offset: 196, }, }, &ruleRefExpr{ - pos: position{line: 1166, col: 5, offset: 35955}, + pos: position{line: 1166, col: 5, offset: 35953}, offset: 303, }, }, @@ -8160,49 +8160,49 @@ var g = &grammar{ }, { name: "ListItemValue", - pos: position{line: 1172, col: 1, offset: 36084}, + pos: position{line: 1172, col: 1, offset: 36082}, expr: &actionExpr{ - pos: position{line: 1172, col: 17, offset: 36100}, + pos: position{line: 1172, col: 17, offset: 36098}, run: (*parser).callonListItemValue1, expr: &seqExpr{ - pos: position{line: 1172, col: 17, offset: 36100}, + pos: position{line: 1172, col: 17, offset: 36098}, exprs: []any{ &labeledExpr{ - pos: position{line: 1172, col: 17, offset: 36100}, + pos: position{line: 1172, col: 17, offset: 36098}, label: "line", expr: &oneOrMoreExpr{ - pos: position{line: 1172, col: 22, offset: 36105}, + pos: position{line: 1172, col: 22, offset: 36103}, expr: &ruleRefExpr{ - pos: position{line: 1172, col: 22, offset: 36105}, + pos: position{line: 1172, col: 22, offset: 36103}, offset: 77, }, }, }, &andExpr{ - pos: position{line: 1172, col: 37, offset: 36120}, + pos: position{line: 1172, col: 37, offset: 36118}, expr: &ruleRefExpr{ - pos: position{line: 1172, col: 38, offset: 36121}, + pos: position{line: 1172, col: 38, offset: 36119}, offset: 299, }, }, &labeledExpr{ - pos: position{line: 1172, col: 48, offset: 36131}, + pos: position{line: 1172, col: 48, offset: 36129}, label: "lines", expr: &zeroOrMoreExpr{ - pos: position{line: 1172, col: 54, offset: 36137}, + pos: position{line: 1172, col: 54, offset: 36135}, expr: &ruleRefExpr{ - pos: position{line: 1172, col: 55, offset: 36138}, + pos: position{line: 1172, col: 55, offset: 36136}, offset: 202, }, }, }, &labeledExpr{ - pos: position{line: 1172, col: 70, offset: 36153}, + pos: position{line: 1172, col: 70, offset: 36151}, label: "attachedBlocks", expr: &zeroOrOneExpr{ - pos: position{line: 1172, col: 85, offset: 36168}, + pos: position{line: 1172, col: 85, offset: 36166}, expr: &ruleRefExpr{ - pos: position{line: 1172, col: 86, offset: 36169}, + pos: position{line: 1172, col: 86, offset: 36167}, offset: 203, }, }, @@ -8213,25 +8213,25 @@ var g = &grammar{ }, { name: "AttachedBlock", - pos: position{line: 1183, col: 1, offset: 36401}, + pos: position{line: 1183, col: 1, offset: 36399}, expr: &actionExpr{ - pos: position{line: 1183, col: 17, offset: 36417}, + pos: position{line: 1183, col: 17, offset: 36415}, run: (*parser).callonAttachedBlock1, expr: &seqExpr{ - pos: position{line: 1183, col: 17, offset: 36417}, + pos: position{line: 1183, col: 17, offset: 36415}, exprs: []any{ &labeledExpr{ - pos: position{line: 1183, col: 17, offset: 36417}, + pos: position{line: 1183, col: 17, offset: 36415}, label: "element", expr: &ruleRefExpr{ - pos: position{line: 1183, col: 26, offset: 36426}, + pos: position{line: 1183, col: 26, offset: 36424}, offset: 200, }, }, &andExpr{ - pos: position{line: 1183, col: 48, offset: 36448}, + pos: position{line: 1183, col: 48, offset: 36446}, expr: &ruleRefExpr{ - pos: position{line: 1183, col: 49, offset: 36449}, + pos: position{line: 1183, col: 49, offset: 36447}, offset: 299, }, }, @@ -8241,32 +8241,32 @@ var g = &grammar{ }, { name: "AttachedBlockElement", - pos: position{line: 1187, col: 1, offset: 36488}, + pos: position{line: 1187, col: 1, offset: 36486}, expr: &actionExpr{ - pos: position{line: 1187, col: 24, offset: 36511}, + pos: position{line: 1187, col: 24, offset: 36509}, run: (*parser).callonAttachedBlockElement1, expr: &seqExpr{ - pos: position{line: 1187, col: 24, offset: 36511}, + pos: position{line: 1187, col: 24, offset: 36509}, exprs: []any{ &litMatcher{ - pos: position{line: 1187, col: 24, offset: 36511}, + pos: position{line: 1187, col: 24, offset: 36509}, val: "+", ignoreCase: false, want: "\"+\"", }, &ruleRefExpr{ - pos: position{line: 1187, col: 28, offset: 36515}, + pos: position{line: 1187, col: 28, offset: 36513}, offset: 303, }, &ruleRefExpr{ - pos: position{line: 1187, col: 38, offset: 36525}, + pos: position{line: 1187, col: 38, offset: 36523}, offset: 287, }, &labeledExpr{ - pos: position{line: 1187, col: 46, offset: 36533}, + pos: position{line: 1187, col: 46, offset: 36531}, label: "value", expr: &ruleRefExpr{ - pos: position{line: 1187, col: 53, offset: 36540}, + pos: position{line: 1187, col: 53, offset: 36538}, offset: 3, }, }, @@ -8276,32 +8276,32 @@ var g = &grammar{ }, { name: "ListContinuation", - pos: position{line: 1191, col: 1, offset: 36628}, + pos: position{line: 1191, col: 1, offset: 36626}, expr: &actionExpr{ - pos: position{line: 1191, col: 20, offset: 36647}, + pos: position{line: 1191, col: 20, offset: 36645}, run: (*parser).callonListContinuation1, expr: &seqExpr{ - pos: position{line: 1191, col: 20, offset: 36647}, + pos: position{line: 1191, col: 20, offset: 36645}, exprs: []any{ &litMatcher{ - pos: position{line: 1191, col: 20, offset: 36647}, + pos: position{line: 1191, col: 20, offset: 36645}, val: "+", ignoreCase: false, want: "\"+\"", }, &ruleRefExpr{ - pos: position{line: 1191, col: 24, offset: 36651}, + pos: position{line: 1191, col: 24, offset: 36649}, offset: 303, }, &ruleRefExpr{ - pos: position{line: 1191, col: 34, offset: 36661}, + pos: position{line: 1191, col: 34, offset: 36659}, offset: 287, }, &labeledExpr{ - pos: position{line: 1191, col: 42, offset: 36669}, + pos: position{line: 1191, col: 42, offset: 36667}, label: "value", expr: &ruleRefExpr{ - pos: position{line: 1191, col: 48, offset: 36675}, + pos: position{line: 1191, col: 48, offset: 36673}, offset: 2, }, }, @@ -8311,53 +8311,53 @@ var g = &grammar{ }, { name: "ListItemLine", - pos: position{line: 1195, col: 1, offset: 36771}, + pos: position{line: 1195, col: 1, offset: 36769}, expr: &actionExpr{ - pos: position{line: 1195, col: 16, offset: 36786}, + pos: position{line: 1195, col: 16, offset: 36784}, run: (*parser).callonListItemLine1, expr: &seqExpr{ - pos: position{line: 1195, col: 16, offset: 36786}, + pos: position{line: 1195, col: 16, offset: 36784}, exprs: []any{ &ruleRefExpr{ - pos: position{line: 1195, col: 16, offset: 36786}, + pos: position{line: 1195, col: 16, offset: 36784}, offset: 299, }, ¬Expr{ - pos: position{line: 1195, col: 26, offset: 36796}, + pos: position{line: 1195, col: 26, offset: 36794}, expr: &ruleRefExpr{ - pos: position{line: 1195, col: 27, offset: 36797}, + pos: position{line: 1195, col: 27, offset: 36795}, offset: 233, }, }, ¬Expr{ - pos: position{line: 1195, col: 45, offset: 36815}, + pos: position{line: 1195, col: 45, offset: 36813}, expr: &ruleRefExpr{ - pos: position{line: 1195, col: 46, offset: 36816}, + pos: position{line: 1195, col: 46, offset: 36814}, offset: 235, }, }, ¬Expr{ - pos: position{line: 1195, col: 66, offset: 36836}, + pos: position{line: 1195, col: 66, offset: 36834}, expr: &ruleRefExpr{ - pos: position{line: 1195, col: 67, offset: 36837}, + pos: position{line: 1195, col: 67, offset: 36835}, offset: 14, }, }, &labeledExpr{ - pos: position{line: 1195, col: 84, offset: 36854}, + pos: position{line: 1195, col: 84, offset: 36852}, label: "element", expr: &oneOrMoreExpr{ - pos: position{line: 1195, col: 92, offset: 36862}, + pos: position{line: 1195, col: 92, offset: 36860}, expr: &ruleRefExpr{ - pos: position{line: 1195, col: 92, offset: 36862}, + pos: position{line: 1195, col: 92, offset: 36860}, offset: 77, }, }, }, &andExpr{ - pos: position{line: 1195, col: 107, offset: 36877}, + pos: position{line: 1195, col: 107, offset: 36875}, expr: &ruleRefExpr{ - pos: position{line: 1195, col: 108, offset: 36878}, + pos: position{line: 1195, col: 108, offset: 36876}, offset: 299, }, }, @@ -8367,28 +8367,28 @@ var g = &grammar{ }, { name: "AttachedBlocks", - pos: position{line: 1199, col: 1, offset: 36945}, + pos: position{line: 1199, col: 1, offset: 36943}, expr: &actionExpr{ - pos: position{line: 1199, col: 18, offset: 36962}, + pos: position{line: 1199, col: 18, offset: 36960}, run: (*parser).callonAttachedBlocks1, expr: &seqExpr{ - pos: position{line: 1199, col: 18, offset: 36962}, + pos: position{line: 1199, col: 18, offset: 36960}, exprs: []any{ &labeledExpr{ - pos: position{line: 1199, col: 18, offset: 36962}, + pos: position{line: 1199, col: 18, offset: 36960}, label: "blocks", expr: &oneOrMoreExpr{ - pos: position{line: 1199, col: 25, offset: 36969}, + pos: position{line: 1199, col: 25, offset: 36967}, expr: &ruleRefExpr{ - pos: position{line: 1199, col: 26, offset: 36970}, + pos: position{line: 1199, col: 26, offset: 36968}, offset: 200, }, }, }, &andExpr{ - pos: position{line: 1199, col: 49, offset: 36993}, + pos: position{line: 1199, col: 49, offset: 36991}, expr: &ruleRefExpr{ - pos: position{line: 1199, col: 50, offset: 36994}, + pos: position{line: 1199, col: 50, offset: 36992}, offset: 299, }, }, @@ -8398,53 +8398,53 @@ var g = &grammar{ }, { name: "ThematicBreak", - pos: position{line: 1204, col: 1, offset: 37033}, + pos: position{line: 1204, col: 1, offset: 37031}, expr: &actionExpr{ - pos: position{line: 1204, col: 17, offset: 37049}, + pos: position{line: 1204, col: 17, offset: 37047}, run: (*parser).callonThematicBreak1, expr: &seqExpr{ - pos: position{line: 1204, col: 17, offset: 37049}, + pos: position{line: 1204, col: 17, offset: 37047}, exprs: []any{ &choiceExpr{ - pos: position{line: 1204, col: 18, offset: 37050}, + pos: position{line: 1204, col: 18, offset: 37048}, alternatives: []any{ &litMatcher{ - pos: position{line: 1204, col: 18, offset: 37050}, + pos: position{line: 1204, col: 18, offset: 37048}, val: "'''", ignoreCase: false, want: "\"'''\"", }, &seqExpr{ - pos: position{line: 1204, col: 26, offset: 37058}, + pos: position{line: 1204, col: 26, offset: 37056}, exprs: []any{ &litMatcher{ - pos: position{line: 1204, col: 26, offset: 37058}, + pos: position{line: 1204, col: 26, offset: 37056}, val: "*", ignoreCase: false, want: "\"*\"", }, &zeroOrOneExpr{ - pos: position{line: 1204, col: 30, offset: 37062}, + pos: position{line: 1204, col: 30, offset: 37060}, expr: &ruleRefExpr{ - pos: position{line: 1204, col: 30, offset: 37062}, + pos: position{line: 1204, col: 30, offset: 37060}, offset: 308, }, }, &litMatcher{ - pos: position{line: 1204, col: 33, offset: 37065}, + pos: position{line: 1204, col: 33, offset: 37063}, val: "*", ignoreCase: false, want: "\"*\"", }, &zeroOrOneExpr{ - pos: position{line: 1204, col: 37, offset: 37069}, + pos: position{line: 1204, col: 37, offset: 37067}, expr: &ruleRefExpr{ - pos: position{line: 1204, col: 37, offset: 37069}, + pos: position{line: 1204, col: 37, offset: 37067}, offset: 308, }, }, &litMatcher{ - pos: position{line: 1204, col: 40, offset: 37072}, + pos: position{line: 1204, col: 40, offset: 37070}, val: "*", ignoreCase: false, want: "\"*\"", @@ -8452,36 +8452,36 @@ var g = &grammar{ }, }, &seqExpr{ - pos: position{line: 1204, col: 46, offset: 37078}, + pos: position{line: 1204, col: 46, offset: 37076}, exprs: []any{ &litMatcher{ - pos: position{line: 1204, col: 46, offset: 37078}, + pos: position{line: 1204, col: 46, offset: 37076}, val: "-", ignoreCase: false, want: "\"-\"", }, &zeroOrOneExpr{ - pos: position{line: 1204, col: 50, offset: 37082}, + pos: position{line: 1204, col: 50, offset: 37080}, expr: &ruleRefExpr{ - pos: position{line: 1204, col: 50, offset: 37082}, + pos: position{line: 1204, col: 50, offset: 37080}, offset: 308, }, }, &litMatcher{ - pos: position{line: 1204, col: 53, offset: 37085}, + pos: position{line: 1204, col: 53, offset: 37083}, val: "-", ignoreCase: false, want: "\"-\"", }, &zeroOrOneExpr{ - pos: position{line: 1204, col: 57, offset: 37089}, + pos: position{line: 1204, col: 57, offset: 37087}, expr: &ruleRefExpr{ - pos: position{line: 1204, col: 57, offset: 37089}, + pos: position{line: 1204, col: 57, offset: 37087}, offset: 308, }, }, &litMatcher{ - pos: position{line: 1204, col: 60, offset: 37092}, + pos: position{line: 1204, col: 60, offset: 37090}, val: "-", ignoreCase: false, want: "\"-\"", @@ -8489,36 +8489,36 @@ var g = &grammar{ }, }, &seqExpr{ - pos: position{line: 1204, col: 66, offset: 37098}, + pos: position{line: 1204, col: 66, offset: 37096}, exprs: []any{ &litMatcher{ - pos: position{line: 1204, col: 66, offset: 37098}, + pos: position{line: 1204, col: 66, offset: 37096}, val: "_", ignoreCase: false, want: "\"_\"", }, &zeroOrOneExpr{ - pos: position{line: 1204, col: 70, offset: 37102}, + pos: position{line: 1204, col: 70, offset: 37100}, expr: &ruleRefExpr{ - pos: position{line: 1204, col: 70, offset: 37102}, + pos: position{line: 1204, col: 70, offset: 37100}, offset: 308, }, }, &litMatcher{ - pos: position{line: 1204, col: 73, offset: 37105}, + pos: position{line: 1204, col: 73, offset: 37103}, val: "_", ignoreCase: false, want: "\"_\"", }, &zeroOrOneExpr{ - pos: position{line: 1204, col: 77, offset: 37109}, + pos: position{line: 1204, col: 77, offset: 37107}, expr: &ruleRefExpr{ - pos: position{line: 1204, col: 77, offset: 37109}, + pos: position{line: 1204, col: 77, offset: 37107}, offset: 308, }, }, &litMatcher{ - pos: position{line: 1204, col: 80, offset: 37112}, + pos: position{line: 1204, col: 80, offset: 37110}, val: "_", ignoreCase: false, want: "\"_\"", @@ -8528,13 +8528,13 @@ var g = &grammar{ }, }, &ruleRefExpr{ - pos: position{line: 1204, col: 85, offset: 37117}, + pos: position{line: 1204, col: 85, offset: 37115}, offset: 303, }, &andExpr{ - pos: position{line: 1204, col: 95, offset: 37127}, + pos: position{line: 1204, col: 95, offset: 37125}, expr: &ruleRefExpr{ - pos: position{line: 1204, col: 96, offset: 37128}, + pos: position{line: 1204, col: 96, offset: 37126}, offset: 299, }, }, @@ -8544,27 +8544,27 @@ var g = &grammar{ }, { name: "PageBreak", - pos: position{line: 1209, col: 1, offset: 37199}, + pos: position{line: 1209, col: 1, offset: 37197}, expr: &actionExpr{ - pos: position{line: 1209, col: 13, offset: 37211}, + pos: position{line: 1209, col: 13, offset: 37209}, run: (*parser).callonPageBreak1, expr: &seqExpr{ - pos: position{line: 1209, col: 13, offset: 37211}, + pos: position{line: 1209, col: 13, offset: 37209}, exprs: []any{ &litMatcher{ - pos: position{line: 1209, col: 14, offset: 37212}, + pos: position{line: 1209, col: 14, offset: 37210}, val: "<<<", ignoreCase: false, want: "\"<<<\"", }, &ruleRefExpr{ - pos: position{line: 1209, col: 21, offset: 37219}, + pos: position{line: 1209, col: 21, offset: 37217}, offset: 303, }, &andExpr{ - pos: position{line: 1209, col: 31, offset: 37229}, + pos: position{line: 1209, col: 31, offset: 37227}, expr: &ruleRefExpr{ - pos: position{line: 1209, col: 32, offset: 37230}, + pos: position{line: 1209, col: 32, offset: 37228}, offset: 299, }, }, @@ -8574,38 +8574,38 @@ var g = &grammar{ }, { name: "Fenced", - pos: position{line: 1214, col: 1, offset: 37297}, + pos: position{line: 1214, col: 1, offset: 37295}, expr: &actionExpr{ - pos: position{line: 1215, col: 5, offset: 37310}, + pos: position{line: 1215, col: 5, offset: 37308}, run: (*parser).callonFenced1, expr: &seqExpr{ - pos: position{line: 1215, col: 5, offset: 37310}, + pos: position{line: 1215, col: 5, offset: 37308}, exprs: []any{ &labeledExpr{ - pos: position{line: 1215, col: 5, offset: 37310}, + pos: position{line: 1215, col: 5, offset: 37308}, label: "start", expr: &ruleRefExpr{ - pos: position{line: 1215, col: 12, offset: 37317}, + pos: position{line: 1215, col: 12, offset: 37315}, offset: 207, }, }, &labeledExpr{ - pos: position{line: 1216, col: 5, offset: 37334}, + pos: position{line: 1216, col: 5, offset: 37332}, label: "lines", expr: &zeroOrMoreExpr{ - pos: position{line: 1216, col: 11, offset: 37340}, + pos: position{line: 1216, col: 11, offset: 37338}, expr: &seqExpr{ - pos: position{line: 1216, col: 12, offset: 37341}, + pos: position{line: 1216, col: 12, offset: 37339}, exprs: []any{ ¬Expr{ - pos: position{line: 1216, col: 12, offset: 37341}, + pos: position{line: 1216, col: 12, offset: 37339}, expr: &ruleRefExpr{ - pos: position{line: 1216, col: 13, offset: 37342}, + pos: position{line: 1216, col: 13, offset: 37340}, offset: 210, }, }, &ruleRefExpr{ - pos: position{line: 1216, col: 29, offset: 37358}, + pos: position{line: 1216, col: 29, offset: 37356}, offset: 1, }, }, @@ -8613,7 +8613,7 @@ var g = &grammar{ }, }, &ruleRefExpr{ - pos: position{line: 1217, col: 5, offset: 37372}, + pos: position{line: 1217, col: 5, offset: 37370}, offset: 208, }, }, @@ -8622,27 +8622,27 @@ var g = &grammar{ }, { name: "FencedStart", - pos: position{line: 1221, col: 1, offset: 37527}, + pos: position{line: 1221, col: 1, offset: 37525}, expr: &actionExpr{ - pos: position{line: 1221, col: 15, offset: 37541}, + pos: position{line: 1221, col: 15, offset: 37539}, run: (*parser).callonFencedStart1, expr: &seqExpr{ - pos: position{line: 1221, col: 15, offset: 37541}, + pos: position{line: 1221, col: 15, offset: 37539}, exprs: []any{ &labeledExpr{ - pos: position{line: 1221, col: 15, offset: 37541}, + pos: position{line: 1221, col: 15, offset: 37539}, label: "delimiter", expr: &ruleRefExpr{ - pos: position{line: 1221, col: 25, offset: 37551}, + pos: position{line: 1221, col: 25, offset: 37549}, offset: 210, }, }, &ruleRefExpr{ - pos: position{line: 1221, col: 41, offset: 37567}, + pos: position{line: 1221, col: 41, offset: 37565}, offset: 299, }, &andCodeExpr{ - pos: position{line: 1221, col: 51, offset: 37577}, + pos: position{line: 1221, col: 51, offset: 37575}, run: (*parser).callonFencedStart6, }, }, @@ -8651,27 +8651,27 @@ var g = &grammar{ }, { name: "FencedEnd", - pos: position{line: 1228, col: 1, offset: 37761}, + pos: position{line: 1228, col: 1, offset: 37759}, expr: &seqExpr{ - pos: position{line: 1228, col: 13, offset: 37773}, + pos: position{line: 1228, col: 13, offset: 37771}, exprs: []any{ &labeledExpr{ - pos: position{line: 1228, col: 13, offset: 37773}, + pos: position{line: 1228, col: 13, offset: 37771}, label: "delimiter", expr: &ruleRefExpr{ - pos: position{line: 1228, col: 23, offset: 37783}, + pos: position{line: 1228, col: 23, offset: 37781}, offset: 210, }, }, &andExpr{ - pos: position{line: 1228, col: 39, offset: 37799}, + pos: position{line: 1228, col: 39, offset: 37797}, expr: &ruleRefExpr{ - pos: position{line: 1228, col: 40, offset: 37800}, + pos: position{line: 1228, col: 40, offset: 37798}, offset: 299, }, }, &andCodeExpr{ - pos: position{line: 1228, col: 50, offset: 37810}, + pos: position{line: 1228, col: 50, offset: 37808}, run: (*parser).callonFencedEnd6, }, }, @@ -8679,29 +8679,29 @@ var g = &grammar{ }, { name: "FencedBackticks", - pos: position{line: 1237, col: 1, offset: 38076}, + pos: position{line: 1237, col: 1, offset: 38074}, expr: &actionExpr{ - pos: position{line: 1237, col: 19, offset: 38094}, + pos: position{line: 1237, col: 19, offset: 38092}, run: (*parser).callonFencedBackticks1, expr: &seqExpr{ - pos: position{line: 1237, col: 19, offset: 38094}, + pos: position{line: 1237, col: 19, offset: 38092}, exprs: []any{ &labeledExpr{ - pos: position{line: 1237, col: 19, offset: 38094}, + pos: position{line: 1237, col: 19, offset: 38092}, label: "backticks", expr: &seqExpr{ - pos: position{line: 1237, col: 30, offset: 38105}, + pos: position{line: 1237, col: 30, offset: 38103}, exprs: []any{ &litMatcher{ - pos: position{line: 1237, col: 30, offset: 38105}, + pos: position{line: 1237, col: 30, offset: 38103}, val: "```", ignoreCase: false, want: "\"```\"", }, &zeroOrMoreExpr{ - pos: position{line: 1237, col: 36, offset: 38111}, + pos: position{line: 1237, col: 36, offset: 38109}, expr: &litMatcher{ - pos: position{line: 1237, col: 36, offset: 38111}, + pos: position{line: 1237, col: 36, offset: 38109}, val: "=", ignoreCase: false, want: "\"=\"", @@ -8711,7 +8711,7 @@ var g = &grammar{ }, }, &andCodeExpr{ - pos: position{line: 1237, col: 42, offset: 38117}, + pos: position{line: 1237, col: 42, offset: 38115}, run: (*parser).callonFencedBackticks8, }, }, @@ -8720,23 +8720,23 @@ var g = &grammar{ }, { name: "FencedDelimiter", - pos: position{line: 1244, col: 1, offset: 38244}, + pos: position{line: 1244, col: 1, offset: 38242}, expr: &actionExpr{ - pos: position{line: 1244, col: 19, offset: 38262}, + pos: position{line: 1244, col: 19, offset: 38260}, run: (*parser).callonFencedDelimiter1, expr: &seqExpr{ - pos: position{line: 1244, col: 19, offset: 38262}, + pos: position{line: 1244, col: 19, offset: 38260}, exprs: []any{ &labeledExpr{ - pos: position{line: 1244, col: 19, offset: 38262}, + pos: position{line: 1244, col: 19, offset: 38260}, label: "hyphens", expr: &ruleRefExpr{ - pos: position{line: 1244, col: 28, offset: 38271}, + pos: position{line: 1244, col: 28, offset: 38269}, offset: 209, }, }, &ruleRefExpr{ - pos: position{line: 1245, col: 5, offset: 38293}, + pos: position{line: 1245, col: 5, offset: 38291}, offset: 303, }, }, @@ -8745,34 +8745,34 @@ var g = &grammar{ }, { name: "FencedLine", - pos: position{line: 1250, col: 1, offset: 38423}, + pos: position{line: 1250, col: 1, offset: 38421}, expr: &actionExpr{ - pos: position{line: 1250, col: 14, offset: 38436}, + pos: position{line: 1250, col: 14, offset: 38434}, run: (*parser).callonFencedLine1, expr: &seqExpr{ - pos: position{line: 1250, col: 15, offset: 38437}, + pos: position{line: 1250, col: 15, offset: 38435}, exprs: []any{ ¬Expr{ - pos: position{line: 1250, col: 15, offset: 38437}, + pos: position{line: 1250, col: 15, offset: 38435}, expr: &ruleRefExpr{ - pos: position{line: 1250, col: 16, offset: 38438}, + pos: position{line: 1250, col: 16, offset: 38436}, offset: 210, }, }, &ruleRefExpr{ - pos: position{line: 1250, col: 32, offset: 38454}, + pos: position{line: 1250, col: 32, offset: 38452}, offset: 295, }, &labeledExpr{ - pos: position{line: 1250, col: 45, offset: 38467}, + pos: position{line: 1250, col: 45, offset: 38465}, label: "line", expr: &ruleRefExpr{ - pos: position{line: 1250, col: 50, offset: 38472}, + pos: position{line: 1250, col: 50, offset: 38470}, offset: 302, }, }, &ruleRefExpr{ - pos: position{line: 1250, col: 58, offset: 38480}, + pos: position{line: 1250, col: 58, offset: 38478}, offset: 299, }, }, @@ -8781,34 +8781,34 @@ var g = &grammar{ }, { name: "Stem", - pos: position{line: 1256, col: 1, offset: 38529}, + pos: position{line: 1256, col: 1, offset: 38527}, expr: &actionExpr{ - pos: position{line: 1257, col: 5, offset: 38540}, + pos: position{line: 1257, col: 5, offset: 38538}, run: (*parser).callonStem1, expr: &seqExpr{ - pos: position{line: 1257, col: 5, offset: 38540}, + pos: position{line: 1257, col: 5, offset: 38538}, exprs: []any{ &labeledExpr{ - pos: position{line: 1257, col: 5, offset: 38540}, + pos: position{line: 1257, col: 5, offset: 38538}, label: "start", expr: &ruleRefExpr{ - pos: position{line: 1257, col: 12, offset: 38547}, + pos: position{line: 1257, col: 12, offset: 38545}, offset: 213, }, }, &labeledExpr{ - pos: position{line: 1258, col: 5, offset: 38562}, + pos: position{line: 1258, col: 5, offset: 38560}, label: "lines", expr: &zeroOrMoreExpr{ - pos: position{line: 1258, col: 11, offset: 38568}, + pos: position{line: 1258, col: 11, offset: 38566}, expr: &ruleRefExpr{ - pos: position{line: 1258, col: 12, offset: 38569}, + pos: position{line: 1258, col: 12, offset: 38567}, offset: 217, }, }, }, &ruleRefExpr{ - pos: position{line: 1259, col: 5, offset: 38584}, + pos: position{line: 1259, col: 5, offset: 38582}, offset: 214, }, }, @@ -8817,27 +8817,27 @@ var g = &grammar{ }, { name: "StemStart", - pos: position{line: 1263, col: 1, offset: 38743}, + pos: position{line: 1263, col: 1, offset: 38741}, expr: &actionExpr{ - pos: position{line: 1263, col: 13, offset: 38755}, + pos: position{line: 1263, col: 13, offset: 38753}, run: (*parser).callonStemStart1, expr: &seqExpr{ - pos: position{line: 1263, col: 13, offset: 38755}, + pos: position{line: 1263, col: 13, offset: 38753}, exprs: []any{ &labeledExpr{ - pos: position{line: 1263, col: 13, offset: 38755}, + pos: position{line: 1263, col: 13, offset: 38753}, label: "delimiter", expr: &ruleRefExpr{ - pos: position{line: 1263, col: 23, offset: 38765}, + pos: position{line: 1263, col: 23, offset: 38763}, offset: 216, }, }, &ruleRefExpr{ - pos: position{line: 1263, col: 37, offset: 38779}, + pos: position{line: 1263, col: 37, offset: 38777}, offset: 299, }, &andCodeExpr{ - pos: position{line: 1263, col: 47, offset: 38789}, + pos: position{line: 1263, col: 47, offset: 38787}, run: (*parser).callonStemStart6, }, }, @@ -8846,27 +8846,27 @@ var g = &grammar{ }, { name: "StemEnd", - pos: position{line: 1270, col: 1, offset: 38963}, + pos: position{line: 1270, col: 1, offset: 38961}, expr: &seqExpr{ - pos: position{line: 1270, col: 11, offset: 38973}, + pos: position{line: 1270, col: 11, offset: 38971}, exprs: []any{ &labeledExpr{ - pos: position{line: 1270, col: 11, offset: 38973}, + pos: position{line: 1270, col: 11, offset: 38971}, label: "delimiter", expr: &ruleRefExpr{ - pos: position{line: 1270, col: 21, offset: 38983}, + pos: position{line: 1270, col: 21, offset: 38981}, offset: 216, }, }, &andExpr{ - pos: position{line: 1270, col: 35, offset: 38997}, + pos: position{line: 1270, col: 35, offset: 38995}, expr: &ruleRefExpr{ - pos: position{line: 1270, col: 36, offset: 38998}, + pos: position{line: 1270, col: 36, offset: 38996}, offset: 299, }, }, &andCodeExpr{ - pos: position{line: 1270, col: 46, offset: 39008}, + pos: position{line: 1270, col: 46, offset: 39006}, run: (*parser).callonStemEnd6, }, }, @@ -8874,29 +8874,29 @@ var g = &grammar{ }, { name: "StemPluses", - pos: position{line: 1279, col: 1, offset: 39262}, + pos: position{line: 1279, col: 1, offset: 39260}, expr: &actionExpr{ - pos: position{line: 1279, col: 14, offset: 39275}, + pos: position{line: 1279, col: 14, offset: 39273}, run: (*parser).callonStemPluses1, expr: &seqExpr{ - pos: position{line: 1279, col: 14, offset: 39275}, + pos: position{line: 1279, col: 14, offset: 39273}, exprs: []any{ &labeledExpr{ - pos: position{line: 1279, col: 14, offset: 39275}, + pos: position{line: 1279, col: 14, offset: 39273}, label: "pluses", expr: &seqExpr{ - pos: position{line: 1279, col: 22, offset: 39283}, + pos: position{line: 1279, col: 22, offset: 39281}, exprs: []any{ &litMatcher{ - pos: position{line: 1279, col: 22, offset: 39283}, + pos: position{line: 1279, col: 22, offset: 39281}, val: "++++", ignoreCase: false, want: "\"++++\"", }, &zeroOrMoreExpr{ - pos: position{line: 1279, col: 29, offset: 39290}, + pos: position{line: 1279, col: 29, offset: 39288}, expr: &litMatcher{ - pos: position{line: 1279, col: 29, offset: 39290}, + pos: position{line: 1279, col: 29, offset: 39288}, val: "+", ignoreCase: false, want: "\"+\"", @@ -8906,7 +8906,7 @@ var g = &grammar{ }, }, &andCodeExpr{ - pos: position{line: 1279, col: 35, offset: 39296}, + pos: position{line: 1279, col: 35, offset: 39294}, run: (*parser).callonStemPluses8, }, }, @@ -8915,23 +8915,23 @@ var g = &grammar{ }, { name: "StemDelimiter", - pos: position{line: 1286, col: 1, offset: 39413}, + pos: position{line: 1286, col: 1, offset: 39411}, expr: &actionExpr{ - pos: position{line: 1286, col: 17, offset: 39429}, + pos: position{line: 1286, col: 17, offset: 39427}, run: (*parser).callonStemDelimiter1, expr: &seqExpr{ - pos: position{line: 1286, col: 17, offset: 39429}, + pos: position{line: 1286, col: 17, offset: 39427}, exprs: []any{ &labeledExpr{ - pos: position{line: 1286, col: 17, offset: 39429}, + pos: position{line: 1286, col: 17, offset: 39427}, label: "pluses", expr: &ruleRefExpr{ - pos: position{line: 1286, col: 25, offset: 39437}, + pos: position{line: 1286, col: 25, offset: 39435}, offset: 215, }, }, &ruleRefExpr{ - pos: position{line: 1287, col: 5, offset: 39454}, + pos: position{line: 1287, col: 5, offset: 39452}, offset: 303, }, }, @@ -8940,34 +8940,34 @@ var g = &grammar{ }, { name: "StemLine", - pos: position{line: 1292, col: 1, offset: 39577}, + pos: position{line: 1292, col: 1, offset: 39575}, expr: &actionExpr{ - pos: position{line: 1292, col: 12, offset: 39588}, + pos: position{line: 1292, col: 12, offset: 39586}, run: (*parser).callonStemLine1, expr: &seqExpr{ - pos: position{line: 1292, col: 13, offset: 39589}, + pos: position{line: 1292, col: 13, offset: 39587}, exprs: []any{ ¬Expr{ - pos: position{line: 1292, col: 13, offset: 39589}, + pos: position{line: 1292, col: 13, offset: 39587}, expr: &ruleRefExpr{ - pos: position{line: 1292, col: 14, offset: 39590}, + pos: position{line: 1292, col: 14, offset: 39588}, offset: 216, }, }, &ruleRefExpr{ - pos: position{line: 1292, col: 28, offset: 39604}, + pos: position{line: 1292, col: 28, offset: 39602}, offset: 295, }, &labeledExpr{ - pos: position{line: 1292, col: 41, offset: 39617}, + pos: position{line: 1292, col: 41, offset: 39615}, label: "line", expr: &ruleRefExpr{ - pos: position{line: 1292, col: 46, offset: 39622}, + pos: position{line: 1292, col: 46, offset: 39620}, offset: 302, }, }, &ruleRefExpr{ - pos: position{line: 1292, col: 54, offset: 39630}, + pos: position{line: 1292, col: 54, offset: 39628}, offset: 299, }, }, @@ -8976,16 +8976,16 @@ var g = &grammar{ }, { name: "CrossReference", - pos: position{line: 1300, col: 1, offset: 39681}, + pos: position{line: 1300, col: 1, offset: 39679}, expr: &choiceExpr{ - pos: position{line: 1300, col: 18, offset: 39698}, + pos: position{line: 1300, col: 18, offset: 39696}, alternatives: []any{ &ruleRefExpr{ - pos: position{line: 1300, col: 18, offset: 39698}, + pos: position{line: 1300, col: 18, offset: 39696}, offset: 219, }, &ruleRefExpr{ - pos: position{line: 1300, col: 43, offset: 39723}, + pos: position{line: 1300, col: 43, offset: 39721}, offset: 223, }, }, @@ -8993,58 +8993,58 @@ var g = &grammar{ }, { name: "InternalCrossReference", - pos: position{line: 1302, col: 1, offset: 39747}, + pos: position{line: 1302, col: 1, offset: 39745}, expr: &actionExpr{ - pos: position{line: 1302, col: 26, offset: 39772}, + pos: position{line: 1302, col: 26, offset: 39770}, run: (*parser).callonInternalCrossReference1, expr: &seqExpr{ - pos: position{line: 1302, col: 26, offset: 39772}, + pos: position{line: 1302, col: 26, offset: 39770}, exprs: []any{ &ruleRefExpr{ - pos: position{line: 1302, col: 26, offset: 39772}, + pos: position{line: 1302, col: 26, offset: 39770}, offset: 220, }, &labeledExpr{ - pos: position{line: 1302, col: 46, offset: 39792}, + pos: position{line: 1302, col: 46, offset: 39790}, label: "id", expr: &ruleRefExpr{ - pos: position{line: 1302, col: 50, offset: 39796}, + pos: position{line: 1302, col: 50, offset: 39794}, offset: 221, }, }, &labeledExpr{ - pos: position{line: 1302, col: 68, offset: 39814}, + pos: position{line: 1302, col: 68, offset: 39812}, label: "label", expr: &zeroOrOneExpr{ - pos: position{line: 1302, col: 74, offset: 39820}, + pos: position{line: 1302, col: 74, offset: 39818}, expr: &actionExpr{ - pos: position{line: 1302, col: 75, offset: 39821}, + pos: position{line: 1302, col: 75, offset: 39819}, run: (*parser).callonInternalCrossReference8, expr: &seqExpr{ - pos: position{line: 1302, col: 75, offset: 39821}, + pos: position{line: 1302, col: 75, offset: 39819}, exprs: []any{ &ruleRefExpr{ - pos: position{line: 1302, col: 75, offset: 39821}, + pos: position{line: 1302, col: 75, offset: 39819}, offset: 303, }, &litMatcher{ - pos: position{line: 1302, col: 85, offset: 39831}, + pos: position{line: 1302, col: 85, offset: 39829}, val: ",", ignoreCase: false, want: "\",\"", }, &labeledExpr{ - pos: position{line: 1302, col: 89, offset: 39835}, + pos: position{line: 1302, col: 89, offset: 39833}, label: "label", expr: &seqExpr{ - pos: position{line: 1302, col: 96, offset: 39842}, + pos: position{line: 1302, col: 96, offset: 39840}, exprs: []any{ &ruleRefExpr{ - pos: position{line: 1302, col: 96, offset: 39842}, + pos: position{line: 1302, col: 96, offset: 39840}, offset: 303, }, &ruleRefExpr{ - pos: position{line: 1302, col: 106, offset: 39852}, + pos: position{line: 1302, col: 106, offset: 39850}, offset: 222, }, }, @@ -9056,7 +9056,7 @@ var g = &grammar{ }, }, &litMatcher{ - pos: position{line: 1302, col: 150, offset: 39896}, + pos: position{line: 1302, col: 150, offset: 39894}, val: ">>", ignoreCase: false, want: "\">>\"", @@ -9067,9 +9067,9 @@ var g = &grammar{ }, { name: "CrossReferenceStart", - pos: position{line: 1310, col: 1, offset: 40100}, + pos: position{line: 1310, col: 1, offset: 40098}, expr: &litMatcher{ - pos: position{line: 1310, col: 23, offset: 40122}, + pos: position{line: 1310, col: 23, offset: 40120}, val: "<<", ignoreCase: false, want: "\"<<\"", @@ -9077,22 +9077,22 @@ var g = &grammar{ }, { name: "CrossReferenceID", - pos: position{line: 1312, col: 1, offset: 40129}, + pos: position{line: 1312, col: 1, offset: 40127}, expr: &actionExpr{ - pos: position{line: 1312, col: 21, offset: 40149}, + pos: position{line: 1312, col: 21, offset: 40147}, run: (*parser).callonCrossReferenceID1, expr: &seqExpr{ - pos: position{line: 1312, col: 21, offset: 40149}, + pos: position{line: 1312, col: 21, offset: 40147}, exprs: []any{ &choiceExpr{ - pos: position{line: 1312, col: 22, offset: 40150}, + pos: position{line: 1312, col: 22, offset: 40148}, alternatives: []any{ &ruleRefExpr{ - pos: position{line: 1312, col: 22, offset: 40150}, + pos: position{line: 1312, col: 22, offset: 40148}, offset: 292, }, &charClassMatcher{ - pos: position{line: 1312, col: 30, offset: 40158}, + pos: position{line: 1312, col: 30, offset: 40156}, val: "[:_]", chars: []rune{':', '_'}, ignoreCase: false, @@ -9101,9 +9101,9 @@ var g = &grammar{ }, }, &zeroOrMoreExpr{ - pos: position{line: 1312, col: 36, offset: 40164}, + pos: position{line: 1312, col: 36, offset: 40162}, expr: &charClassMatcher{ - pos: position{line: 1312, col: 36, offset: 40164}, + pos: position{line: 1312, col: 36, offset: 40162}, val: "[\\p{L}\\p{N} _:.-]", chars: []rune{' ', '_', ':', '.', '-'}, classes: []*unicode.RangeTable{rangeTable("L"), rangeTable("N")}, @@ -9117,16 +9117,16 @@ var g = &grammar{ }, { name: "CrossReferenceLabel", - pos: position{line: 1317, col: 1, offset: 40316}, + pos: position{line: 1317, col: 1, offset: 40314}, expr: &oneOrMoreExpr{ - pos: position{line: 1317, col: 23, offset: 40338}, + pos: position{line: 1317, col: 23, offset: 40336}, expr: &choiceExpr{ - pos: position{line: 1318, col: 5, offset: 40344}, + pos: position{line: 1318, col: 5, offset: 40342}, alternatives: []any{ &oneOrMoreExpr{ - pos: position{line: 1318, col: 6, offset: 40345}, + pos: position{line: 1318, col: 6, offset: 40343}, expr: &charClassMatcher{ - pos: position{line: 1318, col: 6, offset: 40345}, + pos: position{line: 1318, col: 6, offset: 40343}, val: "[^<>{[\\]]", chars: []rune{'<', '>', '{', '[', ']'}, ignoreCase: false, @@ -9134,11 +9134,11 @@ var g = &grammar{ }, }, &ruleRefExpr{ - pos: position{line: 1319, col: 7, offset: 40363}, + pos: position{line: 1319, col: 7, offset: 40361}, offset: 41, }, &litMatcher{ - pos: position{line: 1320, col: 7, offset: 40389}, + pos: position{line: 1320, col: 7, offset: 40387}, val: "{", ignoreCase: false, want: "\"{\"", @@ -9149,32 +9149,32 @@ var g = &grammar{ }, { name: "DocumentCrossReference", - pos: position{line: 1323, col: 1, offset: 40398}, + pos: position{line: 1323, col: 1, offset: 40396}, expr: &actionExpr{ - pos: position{line: 1323, col: 26, offset: 40423}, + pos: position{line: 1323, col: 26, offset: 40421}, run: (*parser).callonDocumentCrossReference1, expr: &seqExpr{ - pos: position{line: 1323, col: 26, offset: 40423}, + pos: position{line: 1323, col: 26, offset: 40421}, exprs: []any{ &litMatcher{ - pos: position{line: 1323, col: 26, offset: 40423}, + pos: position{line: 1323, col: 26, offset: 40421}, val: "xref:", ignoreCase: false, want: "\"xref:\"", }, &labeledExpr{ - pos: position{line: 1323, col: 34, offset: 40431}, + pos: position{line: 1323, col: 34, offset: 40429}, label: "path", expr: &ruleRefExpr{ - pos: position{line: 1323, col: 40, offset: 40437}, + pos: position{line: 1323, col: 40, offset: 40435}, offset: 146, }, }, &labeledExpr{ - pos: position{line: 1323, col: 46, offset: 40443}, + pos: position{line: 1323, col: 46, offset: 40441}, label: "attributes", expr: &ruleRefExpr{ - pos: position{line: 1323, col: 58, offset: 40455}, + pos: position{line: 1323, col: 58, offset: 40453}, offset: 39, }, }, @@ -9184,20 +9184,20 @@ var g = &grammar{ }, { name: "UppercaseRoman", - pos: position{line: 1330, col: 1, offset: 40621}, + pos: position{line: 1330, col: 1, offset: 40619}, expr: &actionExpr{ - pos: position{line: 1330, col: 18, offset: 40638}, + pos: position{line: 1330, col: 18, offset: 40636}, run: (*parser).callonUppercaseRoman1, expr: &seqExpr{ - pos: position{line: 1330, col: 18, offset: 40638}, + pos: position{line: 1330, col: 18, offset: 40636}, exprs: []any{ &labeledExpr{ - pos: position{line: 1330, col: 18, offset: 40638}, + pos: position{line: 1330, col: 18, offset: 40636}, label: "thousands", expr: &zeroOrMoreExpr{ - pos: position{line: 1330, col: 28, offset: 40648}, + pos: position{line: 1330, col: 28, offset: 40646}, expr: &litMatcher{ - pos: position{line: 1330, col: 28, offset: 40648}, + pos: position{line: 1330, col: 28, offset: 40646}, val: "M", ignoreCase: false, want: "\"M\"", @@ -9205,40 +9205,40 @@ var g = &grammar{ }, }, &labeledExpr{ - pos: position{line: 1330, col: 33, offset: 40653}, + pos: position{line: 1330, col: 33, offset: 40651}, label: "hundreds", expr: &zeroOrOneExpr{ - pos: position{line: 1330, col: 42, offset: 40662}, + pos: position{line: 1330, col: 42, offset: 40660}, expr: &ruleRefExpr{ - pos: position{line: 1330, col: 42, offset: 40662}, + pos: position{line: 1330, col: 42, offset: 40660}, offset: 225, }, }, }, &labeledExpr{ - pos: position{line: 1330, col: 66, offset: 40686}, + pos: position{line: 1330, col: 66, offset: 40684}, label: "tens", expr: &zeroOrOneExpr{ - pos: position{line: 1330, col: 71, offset: 40691}, + pos: position{line: 1330, col: 71, offset: 40689}, expr: &ruleRefExpr{ - pos: position{line: 1330, col: 72, offset: 40692}, + pos: position{line: 1330, col: 72, offset: 40690}, offset: 226, }, }, }, &labeledExpr{ - pos: position{line: 1330, col: 93, offset: 40713}, + pos: position{line: 1330, col: 93, offset: 40711}, label: "ones", expr: &zeroOrOneExpr{ - pos: position{line: 1330, col: 98, offset: 40718}, + pos: position{line: 1330, col: 98, offset: 40716}, expr: &ruleRefExpr{ - pos: position{line: 1330, col: 99, offset: 40719}, + pos: position{line: 1330, col: 99, offset: 40717}, offset: 227, }, }, }, &andCodeExpr{ - pos: position{line: 1330, col: 120, offset: 40740}, + pos: position{line: 1330, col: 120, offset: 40738}, run: (*parser).callonUppercaseRoman15, }, }, @@ -9247,38 +9247,38 @@ var g = &grammar{ }, { name: "UppercaseRomanHundreds", - pos: position{line: 1337, col: 1, offset: 40864}, + pos: position{line: 1337, col: 1, offset: 40862}, expr: &choiceExpr{ - pos: position{line: 1338, col: 5, offset: 40895}, + pos: position{line: 1338, col: 5, offset: 40893}, alternatives: []any{ &litMatcher{ - pos: position{line: 1338, col: 5, offset: 40895}, + pos: position{line: 1338, col: 5, offset: 40893}, val: "CM", ignoreCase: false, want: "\"CM\"", }, &litMatcher{ - pos: position{line: 1339, col: 7, offset: 40906}, + pos: position{line: 1339, col: 7, offset: 40904}, val: "CD", ignoreCase: false, want: "\"CD\"", }, &seqExpr{ - pos: position{line: 1340, col: 8, offset: 40918}, + pos: position{line: 1340, col: 8, offset: 40916}, exprs: []any{ &litMatcher{ - pos: position{line: 1340, col: 8, offset: 40918}, + pos: position{line: 1340, col: 8, offset: 40916}, val: "D", ignoreCase: false, want: "\"D\"", }, &labeledExpr{ - pos: position{line: 1340, col: 12, offset: 40922}, + pos: position{line: 1340, col: 12, offset: 40920}, label: "hundreds", expr: &zeroOrMoreExpr{ - pos: position{line: 1340, col: 21, offset: 40931}, + pos: position{line: 1340, col: 21, offset: 40929}, expr: &litMatcher{ - pos: position{line: 1340, col: 21, offset: 40931}, + pos: position{line: 1340, col: 21, offset: 40929}, val: "C", ignoreCase: false, want: "\"C\"", @@ -9286,7 +9286,7 @@ var g = &grammar{ }, }, &andCodeExpr{ - pos: position{line: 1340, col: 26, offset: 40936}, + pos: position{line: 1340, col: 26, offset: 40934}, run: (*parser).callonUppercaseRomanHundreds9, }, }, @@ -9296,41 +9296,41 @@ var g = &grammar{ }, { name: "UppercaseRomanTens", - pos: position{line: 1343, col: 1, offset: 40982}, + pos: position{line: 1343, col: 1, offset: 40980}, expr: &choiceExpr{ - pos: position{line: 1344, col: 5, offset: 41009}, + pos: position{line: 1344, col: 5, offset: 41007}, alternatives: []any{ &litMatcher{ - pos: position{line: 1344, col: 5, offset: 41009}, + pos: position{line: 1344, col: 5, offset: 41007}, val: "XC", ignoreCase: false, want: "\"XC\"", }, &litMatcher{ - pos: position{line: 1345, col: 7, offset: 41020}, + pos: position{line: 1345, col: 7, offset: 41018}, val: "XL", ignoreCase: false, want: "\"XL\"", }, &seqExpr{ - pos: position{line: 1346, col: 8, offset: 41032}, + pos: position{line: 1346, col: 8, offset: 41030}, exprs: []any{ &zeroOrOneExpr{ - pos: position{line: 1346, col: 8, offset: 41032}, + pos: position{line: 1346, col: 8, offset: 41030}, expr: &litMatcher{ - pos: position{line: 1346, col: 8, offset: 41032}, + pos: position{line: 1346, col: 8, offset: 41030}, val: "L", ignoreCase: false, want: "\"L\"", }, }, &labeledExpr{ - pos: position{line: 1346, col: 13, offset: 41037}, + pos: position{line: 1346, col: 13, offset: 41035}, label: "tens", expr: &zeroOrMoreExpr{ - pos: position{line: 1346, col: 18, offset: 41042}, + pos: position{line: 1346, col: 18, offset: 41040}, expr: &litMatcher{ - pos: position{line: 1346, col: 18, offset: 41042}, + pos: position{line: 1346, col: 18, offset: 41040}, val: "X", ignoreCase: false, want: "\"X\"", @@ -9338,7 +9338,7 @@ var g = &grammar{ }, }, &andCodeExpr{ - pos: position{line: 1346, col: 23, offset: 41047}, + pos: position{line: 1346, col: 23, offset: 41045}, run: (*parser).callonUppercaseRomanTens10, }, }, @@ -9348,41 +9348,41 @@ var g = &grammar{ }, { name: "UppercaseRomanOnes", - pos: position{line: 1349, col: 1, offset: 41089}, + pos: position{line: 1349, col: 1, offset: 41087}, expr: &choiceExpr{ - pos: position{line: 1350, col: 5, offset: 41116}, + pos: position{line: 1350, col: 5, offset: 41114}, alternatives: []any{ &litMatcher{ - pos: position{line: 1350, col: 5, offset: 41116}, + pos: position{line: 1350, col: 5, offset: 41114}, val: "IX", ignoreCase: false, want: "\"IX\"", }, &litMatcher{ - pos: position{line: 1351, col: 7, offset: 41127}, + pos: position{line: 1351, col: 7, offset: 41125}, val: "IV", ignoreCase: false, want: "\"IV\"", }, &seqExpr{ - pos: position{line: 1352, col: 8, offset: 41139}, + pos: position{line: 1352, col: 8, offset: 41137}, exprs: []any{ &zeroOrOneExpr{ - pos: position{line: 1352, col: 8, offset: 41139}, + pos: position{line: 1352, col: 8, offset: 41137}, expr: &litMatcher{ - pos: position{line: 1352, col: 8, offset: 41139}, + pos: position{line: 1352, col: 8, offset: 41137}, val: "V", ignoreCase: false, want: "\"V\"", }, }, &labeledExpr{ - pos: position{line: 1352, col: 13, offset: 41144}, + pos: position{line: 1352, col: 13, offset: 41142}, label: "ones", expr: &zeroOrMoreExpr{ - pos: position{line: 1352, col: 18, offset: 41149}, + pos: position{line: 1352, col: 18, offset: 41147}, expr: &litMatcher{ - pos: position{line: 1352, col: 18, offset: 41149}, + pos: position{line: 1352, col: 18, offset: 41147}, val: "I", ignoreCase: false, want: "\"I\"", @@ -9390,7 +9390,7 @@ var g = &grammar{ }, }, &andCodeExpr{ - pos: position{line: 1352, col: 23, offset: 41154}, + pos: position{line: 1352, col: 23, offset: 41152}, run: (*parser).callonUppercaseRomanOnes10, }, }, @@ -9400,20 +9400,20 @@ var g = &grammar{ }, { name: "LowercaseRoman", - pos: position{line: 1355, col: 1, offset: 41196}, + pos: position{line: 1355, col: 1, offset: 41194}, expr: &actionExpr{ - pos: position{line: 1355, col: 18, offset: 41213}, + pos: position{line: 1355, col: 18, offset: 41211}, run: (*parser).callonLowercaseRoman1, expr: &seqExpr{ - pos: position{line: 1355, col: 18, offset: 41213}, + pos: position{line: 1355, col: 18, offset: 41211}, exprs: []any{ &labeledExpr{ - pos: position{line: 1355, col: 18, offset: 41213}, + pos: position{line: 1355, col: 18, offset: 41211}, label: "thousands", expr: &zeroOrMoreExpr{ - pos: position{line: 1355, col: 28, offset: 41223}, + pos: position{line: 1355, col: 28, offset: 41221}, expr: &litMatcher{ - pos: position{line: 1355, col: 28, offset: 41223}, + pos: position{line: 1355, col: 28, offset: 41221}, val: "m", ignoreCase: false, want: "\"m\"", @@ -9421,40 +9421,40 @@ var g = &grammar{ }, }, &labeledExpr{ - pos: position{line: 1355, col: 33, offset: 41228}, + pos: position{line: 1355, col: 33, offset: 41226}, label: "hundreds", expr: &zeroOrOneExpr{ - pos: position{line: 1355, col: 42, offset: 41237}, + pos: position{line: 1355, col: 42, offset: 41235}, expr: &ruleRefExpr{ - pos: position{line: 1355, col: 42, offset: 41237}, + pos: position{line: 1355, col: 42, offset: 41235}, offset: 229, }, }, }, &labeledExpr{ - pos: position{line: 1355, col: 66, offset: 41261}, + pos: position{line: 1355, col: 66, offset: 41259}, label: "tens", expr: &zeroOrOneExpr{ - pos: position{line: 1355, col: 71, offset: 41266}, + pos: position{line: 1355, col: 71, offset: 41264}, expr: &ruleRefExpr{ - pos: position{line: 1355, col: 72, offset: 41267}, + pos: position{line: 1355, col: 72, offset: 41265}, offset: 230, }, }, }, &labeledExpr{ - pos: position{line: 1355, col: 93, offset: 41288}, + pos: position{line: 1355, col: 93, offset: 41286}, label: "ones", expr: &zeroOrOneExpr{ - pos: position{line: 1355, col: 98, offset: 41293}, + pos: position{line: 1355, col: 98, offset: 41291}, expr: &ruleRefExpr{ - pos: position{line: 1355, col: 99, offset: 41294}, + pos: position{line: 1355, col: 99, offset: 41292}, offset: 231, }, }, }, &andCodeExpr{ - pos: position{line: 1355, col: 120, offset: 41315}, + pos: position{line: 1355, col: 120, offset: 41313}, run: (*parser).callonLowercaseRoman15, }, }, @@ -9463,38 +9463,38 @@ var g = &grammar{ }, { name: "LowercaseRomanHundreds", - pos: position{line: 1362, col: 1, offset: 41439}, + pos: position{line: 1362, col: 1, offset: 41437}, expr: &choiceExpr{ - pos: position{line: 1363, col: 5, offset: 41470}, + pos: position{line: 1363, col: 5, offset: 41468}, alternatives: []any{ &litMatcher{ - pos: position{line: 1363, col: 5, offset: 41470}, + pos: position{line: 1363, col: 5, offset: 41468}, val: "cm", ignoreCase: false, want: "\"cm\"", }, &litMatcher{ - pos: position{line: 1364, col: 7, offset: 41481}, + pos: position{line: 1364, col: 7, offset: 41479}, val: "cd", ignoreCase: false, want: "\"cd\"", }, &seqExpr{ - pos: position{line: 1365, col: 8, offset: 41493}, + pos: position{line: 1365, col: 8, offset: 41491}, exprs: []any{ &litMatcher{ - pos: position{line: 1365, col: 8, offset: 41493}, + pos: position{line: 1365, col: 8, offset: 41491}, val: "d", ignoreCase: false, want: "\"d\"", }, &labeledExpr{ - pos: position{line: 1365, col: 12, offset: 41497}, + pos: position{line: 1365, col: 12, offset: 41495}, label: "hundreds", expr: &zeroOrMoreExpr{ - pos: position{line: 1365, col: 21, offset: 41506}, + pos: position{line: 1365, col: 21, offset: 41504}, expr: &litMatcher{ - pos: position{line: 1365, col: 21, offset: 41506}, + pos: position{line: 1365, col: 21, offset: 41504}, val: "c", ignoreCase: false, want: "\"c\"", @@ -9502,7 +9502,7 @@ var g = &grammar{ }, }, &andCodeExpr{ - pos: position{line: 1365, col: 26, offset: 41511}, + pos: position{line: 1365, col: 26, offset: 41509}, run: (*parser).callonLowercaseRomanHundreds9, }, }, @@ -9512,41 +9512,41 @@ var g = &grammar{ }, { name: "LowercaseRomanTens", - pos: position{line: 1368, col: 1, offset: 41557}, + pos: position{line: 1368, col: 1, offset: 41555}, expr: &choiceExpr{ - pos: position{line: 1369, col: 5, offset: 41584}, + pos: position{line: 1369, col: 5, offset: 41582}, alternatives: []any{ &litMatcher{ - pos: position{line: 1369, col: 5, offset: 41584}, + pos: position{line: 1369, col: 5, offset: 41582}, val: "xc", ignoreCase: false, want: "\"xc\"", }, &litMatcher{ - pos: position{line: 1370, col: 7, offset: 41595}, + pos: position{line: 1370, col: 7, offset: 41593}, val: "xl", ignoreCase: false, want: "\"xl\"", }, &seqExpr{ - pos: position{line: 1371, col: 8, offset: 41607}, + pos: position{line: 1371, col: 8, offset: 41605}, exprs: []any{ &zeroOrOneExpr{ - pos: position{line: 1371, col: 8, offset: 41607}, + pos: position{line: 1371, col: 8, offset: 41605}, expr: &litMatcher{ - pos: position{line: 1371, col: 8, offset: 41607}, + pos: position{line: 1371, col: 8, offset: 41605}, val: "l", ignoreCase: false, want: "\"l\"", }, }, &labeledExpr{ - pos: position{line: 1371, col: 13, offset: 41612}, + pos: position{line: 1371, col: 13, offset: 41610}, label: "tens", expr: &zeroOrMoreExpr{ - pos: position{line: 1371, col: 18, offset: 41617}, + pos: position{line: 1371, col: 18, offset: 41615}, expr: &litMatcher{ - pos: position{line: 1371, col: 18, offset: 41617}, + pos: position{line: 1371, col: 18, offset: 41615}, val: "x", ignoreCase: false, want: "\"x\"", @@ -9554,7 +9554,7 @@ var g = &grammar{ }, }, &andCodeExpr{ - pos: position{line: 1371, col: 23, offset: 41622}, + pos: position{line: 1371, col: 23, offset: 41620}, run: (*parser).callonLowercaseRomanTens10, }, }, @@ -9564,41 +9564,41 @@ var g = &grammar{ }, { name: "LowercaseRomanOnes", - pos: position{line: 1374, col: 1, offset: 41664}, + pos: position{line: 1374, col: 1, offset: 41662}, expr: &choiceExpr{ - pos: position{line: 1375, col: 5, offset: 41691}, + pos: position{line: 1375, col: 5, offset: 41689}, alternatives: []any{ &litMatcher{ - pos: position{line: 1375, col: 5, offset: 41691}, + pos: position{line: 1375, col: 5, offset: 41689}, val: "ix", ignoreCase: false, want: "\"ix\"", }, &litMatcher{ - pos: position{line: 1376, col: 7, offset: 41702}, + pos: position{line: 1376, col: 7, offset: 41700}, val: "iv", ignoreCase: false, want: "\"iv\"", }, &seqExpr{ - pos: position{line: 1377, col: 8, offset: 41714}, + pos: position{line: 1377, col: 8, offset: 41712}, exprs: []any{ &zeroOrOneExpr{ - pos: position{line: 1377, col: 8, offset: 41714}, + pos: position{line: 1377, col: 8, offset: 41712}, expr: &litMatcher{ - pos: position{line: 1377, col: 8, offset: 41714}, + pos: position{line: 1377, col: 8, offset: 41712}, val: "v", ignoreCase: false, want: "\"v\"", }, }, &labeledExpr{ - pos: position{line: 1377, col: 13, offset: 41719}, + pos: position{line: 1377, col: 13, offset: 41717}, label: "ones", expr: &zeroOrMoreExpr{ - pos: position{line: 1377, col: 18, offset: 41724}, + pos: position{line: 1377, col: 18, offset: 41722}, expr: &litMatcher{ - pos: position{line: 1377, col: 18, offset: 41724}, + pos: position{line: 1377, col: 18, offset: 41722}, val: "i", ignoreCase: false, want: "\"i\"", @@ -9606,7 +9606,7 @@ var g = &grammar{ }, }, &andCodeExpr{ - pos: position{line: 1377, col: 23, offset: 41729}, + pos: position{line: 1377, col: 23, offset: 41727}, run: (*parser).callonLowercaseRomanOnes10, }, }, @@ -9616,26 +9616,26 @@ var g = &grammar{ }, { name: "OrderedListItem", - pos: position{line: 1382, col: 1, offset: 41773}, + pos: position{line: 1382, col: 1, offset: 41771}, expr: &actionExpr{ - pos: position{line: 1382, col: 19, offset: 41791}, + pos: position{line: 1382, col: 19, offset: 41789}, run: (*parser).callonOrderedListItem1, expr: &seqExpr{ - pos: position{line: 1382, col: 19, offset: 41791}, + pos: position{line: 1382, col: 19, offset: 41789}, exprs: []any{ &labeledExpr{ - pos: position{line: 1382, col: 19, offset: 41791}, + pos: position{line: 1382, col: 19, offset: 41789}, label: "marker", expr: &ruleRefExpr{ - pos: position{line: 1382, col: 27, offset: 41799}, + pos: position{line: 1382, col: 27, offset: 41797}, offset: 233, }, }, &labeledExpr{ - pos: position{line: 1382, col: 47, offset: 41819}, + pos: position{line: 1382, col: 47, offset: 41817}, label: "value", expr: &ruleRefExpr{ - pos: position{line: 1382, col: 54, offset: 41826}, + pos: position{line: 1382, col: 54, offset: 41824}, offset: 198, }, }, @@ -9645,43 +9645,43 @@ var g = &grammar{ }, { name: "OrderedListMarker", - pos: position{line: 1388, col: 1, offset: 42015}, + pos: position{line: 1388, col: 1, offset: 42013}, expr: &actionExpr{ - pos: position{line: 1388, col: 21, offset: 42035}, + pos: position{line: 1388, col: 21, offset: 42033}, run: (*parser).callonOrderedListMarker1, expr: &seqExpr{ - pos: position{line: 1388, col: 21, offset: 42035}, + pos: position{line: 1388, col: 21, offset: 42033}, exprs: []any{ &labeledExpr{ - pos: position{line: 1388, col: 21, offset: 42035}, + pos: position{line: 1388, col: 21, offset: 42033}, label: "indent", expr: &ruleRefExpr{ - pos: position{line: 1388, col: 29, offset: 42043}, + pos: position{line: 1388, col: 29, offset: 42041}, offset: 303, }, }, &labeledExpr{ - pos: position{line: 1388, col: 40, offset: 42054}, + pos: position{line: 1388, col: 40, offset: 42052}, label: "marker", expr: &choiceExpr{ - pos: position{line: 1388, col: 48, offset: 42062}, + pos: position{line: 1388, col: 48, offset: 42060}, alternatives: []any{ &actionExpr{ - pos: position{line: 1388, col: 48, offset: 42062}, + pos: position{line: 1388, col: 48, offset: 42060}, run: (*parser).callonOrderedListMarker7, expr: &seqExpr{ - pos: position{line: 1388, col: 48, offset: 42062}, + pos: position{line: 1388, col: 48, offset: 42060}, exprs: []any{ &labeledExpr{ - pos: position{line: 1388, col: 48, offset: 42062}, + pos: position{line: 1388, col: 48, offset: 42060}, label: "ordinal", expr: &actionExpr{ - pos: position{line: 1388, col: 57, offset: 42071}, + pos: position{line: 1388, col: 57, offset: 42069}, run: (*parser).callonOrderedListMarker10, expr: &oneOrMoreExpr{ - pos: position{line: 1388, col: 58, offset: 42072}, + pos: position{line: 1388, col: 58, offset: 42070}, expr: &litMatcher{ - pos: position{line: 1388, col: 58, offset: 42072}, + pos: position{line: 1388, col: 58, offset: 42070}, val: ".", ignoreCase: false, want: "\".\"", @@ -9690,22 +9690,22 @@ var g = &grammar{ }, }, &andCodeExpr{ - pos: position{line: 1390, col: 4, offset: 42118}, + pos: position{line: 1390, col: 4, offset: 42116}, run: (*parser).callonOrderedListMarker13, }, }, }, }, &actionExpr{ - pos: position{line: 1396, col: 3, offset: 42213}, + pos: position{line: 1396, col: 3, offset: 42211}, run: (*parser).callonOrderedListMarker14, expr: &seqExpr{ - pos: position{line: 1396, col: 3, offset: 42213}, + pos: position{line: 1396, col: 3, offset: 42211}, exprs: []any{ &oneOrMoreExpr{ - pos: position{line: 1396, col: 3, offset: 42213}, + pos: position{line: 1396, col: 3, offset: 42211}, expr: &charClassMatcher{ - pos: position{line: 1396, col: 3, offset: 42213}, + pos: position{line: 1396, col: 3, offset: 42211}, val: "[0-9]", ranges: []rune{'0', '9'}, ignoreCase: false, @@ -9713,7 +9713,7 @@ var g = &grammar{ }, }, &litMatcher{ - pos: position{line: 1396, col: 10, offset: 42220}, + pos: position{line: 1396, col: 10, offset: 42218}, val: ".", ignoreCase: false, want: "\".\"", @@ -9722,20 +9722,20 @@ var g = &grammar{ }, }, &actionExpr{ - pos: position{line: 1397, col: 3, offset: 42257}, + pos: position{line: 1397, col: 3, offset: 42255}, run: (*parser).callonOrderedListMarker19, expr: &seqExpr{ - pos: position{line: 1397, col: 3, offset: 42257}, + pos: position{line: 1397, col: 3, offset: 42255}, exprs: []any{ &charClassMatcher{ - pos: position{line: 1397, col: 3, offset: 42257}, + pos: position{line: 1397, col: 3, offset: 42255}, val: "[a-z]", ranges: []rune{'a', 'z'}, ignoreCase: false, inverted: false, }, &litMatcher{ - pos: position{line: 1397, col: 9, offset: 42263}, + pos: position{line: 1397, col: 9, offset: 42261}, val: ".", ignoreCase: false, want: "\".\"", @@ -9744,20 +9744,20 @@ var g = &grammar{ }, }, &actionExpr{ - pos: position{line: 1398, col: 3, offset: 42300}, + pos: position{line: 1398, col: 3, offset: 42298}, run: (*parser).callonOrderedListMarker23, expr: &seqExpr{ - pos: position{line: 1398, col: 3, offset: 42300}, + pos: position{line: 1398, col: 3, offset: 42298}, exprs: []any{ &charClassMatcher{ - pos: position{line: 1398, col: 3, offset: 42300}, + pos: position{line: 1398, col: 3, offset: 42298}, val: "[A-Z]", ranges: []rune{'A', 'Z'}, ignoreCase: false, inverted: false, }, &litMatcher{ - pos: position{line: 1398, col: 9, offset: 42306}, + pos: position{line: 1398, col: 9, offset: 42304}, val: ".", ignoreCase: false, want: "\".\"", @@ -9766,17 +9766,17 @@ var g = &grammar{ }, }, &actionExpr{ - pos: position{line: 1399, col: 3, offset: 42343}, + pos: position{line: 1399, col: 3, offset: 42341}, run: (*parser).callonOrderedListMarker27, expr: &seqExpr{ - pos: position{line: 1399, col: 3, offset: 42343}, + pos: position{line: 1399, col: 3, offset: 42341}, exprs: []any{ &ruleRefExpr{ - pos: position{line: 1399, col: 3, offset: 42343}, + pos: position{line: 1399, col: 3, offset: 42341}, offset: 228, }, &litMatcher{ - pos: position{line: 1399, col: 18, offset: 42358}, + pos: position{line: 1399, col: 18, offset: 42356}, val: ")", ignoreCase: false, want: "\")\"", @@ -9785,17 +9785,17 @@ var g = &grammar{ }, }, &actionExpr{ - pos: position{line: 1400, col: 3, offset: 42395}, + pos: position{line: 1400, col: 3, offset: 42393}, run: (*parser).callonOrderedListMarker31, expr: &seqExpr{ - pos: position{line: 1400, col: 3, offset: 42395}, + pos: position{line: 1400, col: 3, offset: 42393}, exprs: []any{ &ruleRefExpr{ - pos: position{line: 1400, col: 3, offset: 42395}, + pos: position{line: 1400, col: 3, offset: 42393}, offset: 224, }, &litMatcher{ - pos: position{line: 1400, col: 18, offset: 42410}, + pos: position{line: 1400, col: 18, offset: 42408}, val: ")", ignoreCase: false, want: "\")\"", @@ -9807,7 +9807,7 @@ var g = &grammar{ }, }, &ruleRefExpr{ - pos: position{line: 1402, col: 1, offset: 42447}, + pos: position{line: 1402, col: 1, offset: 42445}, offset: 307, }, }, @@ -9816,37 +9816,37 @@ var g = &grammar{ }, { name: "UnorderedListItem", - pos: position{line: 1410, col: 1, offset: 42538}, + pos: position{line: 1410, col: 1, offset: 42536}, expr: &actionExpr{ - pos: position{line: 1410, col: 21, offset: 42558}, + pos: position{line: 1410, col: 21, offset: 42556}, run: (*parser).callonUnorderedListItem1, expr: &seqExpr{ - pos: position{line: 1410, col: 21, offset: 42558}, + pos: position{line: 1410, col: 21, offset: 42556}, exprs: []any{ &labeledExpr{ - pos: position{line: 1410, col: 21, offset: 42558}, + pos: position{line: 1410, col: 21, offset: 42556}, label: "marker", expr: &ruleRefExpr{ - pos: position{line: 1410, col: 29, offset: 42566}, + pos: position{line: 1410, col: 29, offset: 42564}, offset: 235, }, }, &labeledExpr{ - pos: position{line: 1410, col: 50, offset: 42587}, + pos: position{line: 1410, col: 50, offset: 42585}, label: "checklist", expr: &zeroOrOneExpr{ - pos: position{line: 1410, col: 60, offset: 42597}, + pos: position{line: 1410, col: 60, offset: 42595}, expr: &ruleRefExpr{ - pos: position{line: 1410, col: 61, offset: 42598}, + pos: position{line: 1410, col: 61, offset: 42596}, offset: 236, }, }, }, &labeledExpr{ - pos: position{line: 1410, col: 73, offset: 42610}, + pos: position{line: 1410, col: 73, offset: 42608}, label: "value", expr: &ruleRefExpr{ - pos: position{line: 1410, col: 80, offset: 42617}, + pos: position{line: 1410, col: 80, offset: 42615}, offset: 198, }, }, @@ -9856,43 +9856,43 @@ var g = &grammar{ }, { name: "UnorderedListMarker", - pos: position{line: 1417, col: 1, offset: 42856}, + pos: position{line: 1417, col: 1, offset: 42854}, expr: &actionExpr{ - pos: position{line: 1417, col: 23, offset: 42878}, + pos: position{line: 1417, col: 23, offset: 42876}, run: (*parser).callonUnorderedListMarker1, expr: &seqExpr{ - pos: position{line: 1417, col: 23, offset: 42878}, + pos: position{line: 1417, col: 23, offset: 42876}, exprs: []any{ &labeledExpr{ - pos: position{line: 1417, col: 23, offset: 42878}, + pos: position{line: 1417, col: 23, offset: 42876}, label: "indent", expr: &ruleRefExpr{ - pos: position{line: 1417, col: 31, offset: 42886}, + pos: position{line: 1417, col: 31, offset: 42884}, offset: 303, }, }, &labeledExpr{ - pos: position{line: 1417, col: 42, offset: 42897}, + pos: position{line: 1417, col: 42, offset: 42895}, label: "marker", expr: &actionExpr{ - pos: position{line: 1417, col: 50, offset: 42905}, + pos: position{line: 1417, col: 50, offset: 42903}, run: (*parser).callonUnorderedListMarker6, expr: &choiceExpr{ - pos: position{line: 1417, col: 51, offset: 42906}, + pos: position{line: 1417, col: 51, offset: 42904}, alternatives: []any{ &oneOrMoreExpr{ - pos: position{line: 1417, col: 51, offset: 42906}, + pos: position{line: 1417, col: 51, offset: 42904}, expr: &litMatcher{ - pos: position{line: 1417, col: 51, offset: 42906}, + pos: position{line: 1417, col: 51, offset: 42904}, val: "*", ignoreCase: false, want: "\"*\"", }, }, &oneOrMoreExpr{ - pos: position{line: 1417, col: 58, offset: 42913}, + pos: position{line: 1417, col: 58, offset: 42911}, expr: &litMatcher{ - pos: position{line: 1417, col: 58, offset: 42913}, + pos: position{line: 1417, col: 58, offset: 42911}, val: "-", ignoreCase: false, want: "\"-\"", @@ -9903,11 +9903,11 @@ var g = &grammar{ }, }, &andCodeExpr{ - pos: position{line: 1419, col: 4, offset: 42955}, + pos: position{line: 1419, col: 4, offset: 42953}, run: (*parser).callonUnorderedListMarker12, }, &ruleRefExpr{ - pos: position{line: 1422, col: 1, offset: 43004}, + pos: position{line: 1422, col: 1, offset: 43002}, offset: 307, }, }, @@ -9916,53 +9916,53 @@ var g = &grammar{ }, { name: "Checklist", - pos: position{line: 1428, col: 1, offset: 43089}, + pos: position{line: 1428, col: 1, offset: 43087}, expr: &actionExpr{ - pos: position{line: 1428, col: 13, offset: 43101}, + pos: position{line: 1428, col: 13, offset: 43099}, run: (*parser).callonChecklist1, expr: &seqExpr{ - pos: position{line: 1428, col: 13, offset: 43101}, + pos: position{line: 1428, col: 13, offset: 43099}, exprs: []any{ &andExpr{ - pos: position{line: 1428, col: 13, offset: 43101}, + pos: position{line: 1428, col: 13, offset: 43099}, expr: &litMatcher{ - pos: position{line: 1428, col: 14, offset: 43102}, + pos: position{line: 1428, col: 14, offset: 43100}, val: "[", ignoreCase: false, want: "\"[\"", }, }, &labeledExpr{ - pos: position{line: 1429, col: 1, offset: 43107}, + pos: position{line: 1429, col: 1, offset: 43105}, label: "checklist", expr: &choiceExpr{ - pos: position{line: 1430, col: 7, offset: 43125}, + pos: position{line: 1430, col: 7, offset: 43123}, alternatives: []any{ &actionExpr{ - pos: position{line: 1430, col: 7, offset: 43125}, + pos: position{line: 1430, col: 7, offset: 43123}, run: (*parser).callonChecklist7, expr: &litMatcher{ - pos: position{line: 1430, col: 7, offset: 43125}, + pos: position{line: 1430, col: 7, offset: 43123}, val: "[ ]", ignoreCase: false, want: "\"[ ]\"", }, }, &actionExpr{ - pos: position{line: 1431, col: 7, offset: 43182}, + pos: position{line: 1431, col: 7, offset: 43180}, run: (*parser).callonChecklist9, expr: &litMatcher{ - pos: position{line: 1431, col: 7, offset: 43182}, + pos: position{line: 1431, col: 7, offset: 43180}, val: "[*]", ignoreCase: false, want: "\"[*]\"", }, }, &actionExpr{ - pos: position{line: 1432, col: 7, offset: 43237}, + pos: position{line: 1432, col: 7, offset: 43235}, run: (*parser).callonChecklist11, expr: &litMatcher{ - pos: position{line: 1432, col: 7, offset: 43237}, + pos: position{line: 1432, col: 7, offset: 43235}, val: "[x]", ignoreCase: false, want: "\"[x]\"", @@ -9972,7 +9972,7 @@ var g = &grammar{ }, }, &ruleRefExpr{ - pos: position{line: 1433, col: 7, offset: 43291}, + pos: position{line: 1433, col: 7, offset: 43289}, offset: 307, }, }, @@ -9981,58 +9981,58 @@ var g = &grammar{ }, { name: "DescriptionListItem", - pos: position{line: 1438, col: 1, offset: 43334}, + pos: position{line: 1438, col: 1, offset: 43332}, expr: &actionExpr{ - pos: position{line: 1438, col: 23, offset: 43356}, + pos: position{line: 1438, col: 23, offset: 43354}, run: (*parser).callonDescriptionListItem1, expr: &seqExpr{ - pos: position{line: 1438, col: 23, offset: 43356}, + pos: position{line: 1438, col: 23, offset: 43354}, exprs: []any{ &labeledExpr{ - pos: position{line: 1438, col: 23, offset: 43356}, + pos: position{line: 1438, col: 23, offset: 43354}, label: "inlineAnchor", expr: &zeroOrOneExpr{ - pos: position{line: 1438, col: 36, offset: 43369}, + pos: position{line: 1438, col: 36, offset: 43367}, expr: &ruleRefExpr{ - pos: position{line: 1438, col: 37, offset: 43370}, + pos: position{line: 1438, col: 37, offset: 43368}, offset: 73, }, }, }, &labeledExpr{ - pos: position{line: 1438, col: 55, offset: 43388}, + pos: position{line: 1438, col: 55, offset: 43386}, label: "term", expr: &ruleRefExpr{ - pos: position{line: 1438, col: 61, offset: 43394}, + pos: position{line: 1438, col: 61, offset: 43392}, offset: 238, }, }, &labeledExpr{ - pos: position{line: 1438, col: 82, offset: 43415}, + pos: position{line: 1438, col: 82, offset: 43413}, label: "marker", expr: &ruleRefExpr{ - pos: position{line: 1438, col: 90, offset: 43423}, + pos: position{line: 1438, col: 90, offset: 43421}, offset: 239, }, }, &labeledExpr{ - pos: position{line: 1438, col: 113, offset: 43446}, + pos: position{line: 1438, col: 113, offset: 43444}, label: "whitespace", expr: &oneOrMoreExpr{ - pos: position{line: 1438, col: 124, offset: 43457}, + pos: position{line: 1438, col: 124, offset: 43455}, expr: &choiceExpr{ - pos: position{line: 1438, col: 125, offset: 43458}, + pos: position{line: 1438, col: 125, offset: 43456}, alternatives: []any{ &actionExpr{ - pos: position{line: 1438, col: 125, offset: 43458}, + pos: position{line: 1438, col: 125, offset: 43456}, run: (*parser).callonDescriptionListItem13, expr: &ruleRefExpr{ - pos: position{line: 1438, col: 125, offset: 43458}, + pos: position{line: 1438, col: 125, offset: 43456}, offset: 308, }, }, &ruleRefExpr{ - pos: position{line: 1438, col: 180, offset: 43513}, + pos: position{line: 1438, col: 180, offset: 43511}, offset: 287, }, }, @@ -10040,10 +10040,10 @@ var g = &grammar{ }, }, &labeledExpr{ - pos: position{line: 1438, col: 190, offset: 43523}, + pos: position{line: 1438, col: 190, offset: 43521}, label: "value", expr: &ruleRefExpr{ - pos: position{line: 1438, col: 197, offset: 43530}, + pos: position{line: 1438, col: 197, offset: 43528}, offset: 240, }, }, @@ -10053,34 +10053,34 @@ var g = &grammar{ }, { name: "DescriptionListTerm", - pos: position{line: 1449, col: 1, offset: 44042}, + pos: position{line: 1449, col: 1, offset: 44040}, expr: &actionExpr{ - pos: position{line: 1449, col: 23, offset: 44064}, + pos: position{line: 1449, col: 23, offset: 44062}, run: (*parser).callonDescriptionListTerm1, expr: &labeledExpr{ - pos: position{line: 1449, col: 23, offset: 44064}, + pos: position{line: 1449, col: 23, offset: 44062}, label: "term", expr: &oneOrMoreExpr{ - pos: position{line: 1449, col: 28, offset: 44069}, + pos: position{line: 1449, col: 28, offset: 44067}, expr: &seqExpr{ - pos: position{line: 1449, col: 29, offset: 44070}, + pos: position{line: 1449, col: 29, offset: 44068}, exprs: []any{ ¬Expr{ - pos: position{line: 1449, col: 29, offset: 44070}, + pos: position{line: 1449, col: 29, offset: 44068}, expr: &ruleRefExpr{ - pos: position{line: 1449, col: 30, offset: 44071}, + pos: position{line: 1449, col: 30, offset: 44069}, offset: 239, }, }, ¬Expr{ - pos: position{line: 1449, col: 52, offset: 44093}, + pos: position{line: 1449, col: 52, offset: 44091}, expr: &ruleRefExpr{ - pos: position{line: 1449, col: 53, offset: 44094}, + pos: position{line: 1449, col: 53, offset: 44092}, offset: 299, }, }, &ruleRefExpr{ - pos: position{line: 1449, col: 63, offset: 44104}, + pos: position{line: 1449, col: 63, offset: 44102}, offset: 77, }, }, @@ -10091,36 +10091,36 @@ var g = &grammar{ }, { name: "DescriptionListMarker", - pos: position{line: 1453, col: 1, offset: 44160}, + pos: position{line: 1453, col: 1, offset: 44158}, expr: &actionExpr{ - pos: position{line: 1453, col: 25, offset: 44184}, + pos: position{line: 1453, col: 25, offset: 44182}, run: (*parser).callonDescriptionListMarker1, expr: &seqExpr{ - pos: position{line: 1453, col: 25, offset: 44184}, + pos: position{line: 1453, col: 25, offset: 44182}, exprs: []any{ &ruleRefExpr{ - pos: position{line: 1453, col: 25, offset: 44184}, + pos: position{line: 1453, col: 25, offset: 44182}, offset: 303, }, &labeledExpr{ - pos: position{line: 1453, col: 35, offset: 44194}, + pos: position{line: 1453, col: 35, offset: 44192}, label: "marker", expr: &actionExpr{ - pos: position{line: 1453, col: 43, offset: 44202}, + pos: position{line: 1453, col: 43, offset: 44200}, run: (*parser).callonDescriptionListMarker5, expr: &oneOrMoreExpr{ - pos: position{line: 1453, col: 43, offset: 44202}, + pos: position{line: 1453, col: 43, offset: 44200}, expr: &choiceExpr{ - pos: position{line: 1453, col: 44, offset: 44203}, + pos: position{line: 1453, col: 44, offset: 44201}, alternatives: []any{ &litMatcher{ - pos: position{line: 1453, col: 44, offset: 44203}, + pos: position{line: 1453, col: 44, offset: 44201}, val: ":", ignoreCase: false, want: "\":\"", }, &litMatcher{ - pos: position{line: 1453, col: 50, offset: 44209}, + pos: position{line: 1453, col: 50, offset: 44207}, val: ";", ignoreCase: false, want: "\";\"", @@ -10131,11 +10131,11 @@ var g = &grammar{ }, }, &andCodeExpr{ - pos: position{line: 1455, col: 4, offset: 44251}, + pos: position{line: 1455, col: 4, offset: 44249}, run: (*parser).callonDescriptionListMarker10, }, &ruleRefExpr{ - pos: position{line: 1462, col: 1, offset: 44378}, + pos: position{line: 1462, col: 1, offset: 44376}, offset: 303, }, }, @@ -10144,48 +10144,48 @@ var g = &grammar{ }, { name: "DescriptionListItemValue", - pos: position{line: 1467, col: 1, offset: 44425}, + pos: position{line: 1467, col: 1, offset: 44423}, expr: &actionExpr{ - pos: position{line: 1467, col: 28, offset: 44452}, + pos: position{line: 1467, col: 28, offset: 44450}, run: (*parser).callonDescriptionListItemValue1, expr: &seqExpr{ - pos: position{line: 1467, col: 28, offset: 44452}, + pos: position{line: 1467, col: 28, offset: 44450}, exprs: []any{ &labeledExpr{ - pos: position{line: 1467, col: 28, offset: 44452}, + pos: position{line: 1467, col: 28, offset: 44450}, label: "line", expr: &choiceExpr{ - pos: position{line: 1467, col: 34, offset: 44458}, + pos: position{line: 1467, col: 34, offset: 44456}, alternatives: []any{ &oneOrMoreExpr{ - pos: position{line: 1467, col: 34, offset: 44458}, + pos: position{line: 1467, col: 34, offset: 44456}, expr: &ruleRefExpr{ - pos: position{line: 1467, col: 34, offset: 44458}, + pos: position{line: 1467, col: 34, offset: 44456}, offset: 77, }, }, &ruleRefExpr{ - pos: position{line: 1467, col: 51, offset: 44475}, + pos: position{line: 1467, col: 51, offset: 44473}, offset: 2, }, }, }, }, &labeledExpr{ - pos: position{line: 1467, col: 65, offset: 44489}, + pos: position{line: 1467, col: 65, offset: 44487}, label: "attachedBlocks", expr: &zeroOrOneExpr{ - pos: position{line: 1467, col: 80, offset: 44504}, + pos: position{line: 1467, col: 80, offset: 44502}, expr: &ruleRefExpr{ - pos: position{line: 1467, col: 81, offset: 44505}, + pos: position{line: 1467, col: 81, offset: 44503}, offset: 203, }, }, }, &andExpr{ - pos: position{line: 1467, col: 99, offset: 44523}, + pos: position{line: 1467, col: 99, offset: 44521}, expr: &ruleRefExpr{ - pos: position{line: 1467, col: 100, offset: 44524}, + pos: position{line: 1467, col: 100, offset: 44522}, offset: 299, }, }, @@ -10195,38 +10195,38 @@ var g = &grammar{ }, { name: "SingleLineComment", - pos: position{line: 1476, col: 1, offset: 44685}, + pos: position{line: 1476, col: 1, offset: 44683}, expr: &actionExpr{ - pos: position{line: 1476, col: 21, offset: 44705}, + pos: position{line: 1476, col: 21, offset: 44703}, run: (*parser).callonSingleLineComment1, expr: &seqExpr{ - pos: position{line: 1476, col: 21, offset: 44705}, + pos: position{line: 1476, col: 21, offset: 44703}, exprs: []any{ &ruleRefExpr{ - pos: position{line: 1476, col: 21, offset: 44705}, + pos: position{line: 1476, col: 21, offset: 44703}, offset: 242, }, ¬Expr{ - pos: position{line: 1476, col: 48, offset: 44732}, + pos: position{line: 1476, col: 48, offset: 44730}, expr: &litMatcher{ - pos: position{line: 1476, col: 49, offset: 44733}, + pos: position{line: 1476, col: 49, offset: 44731}, val: "//", ignoreCase: false, want: "\"//\"", }, }, &labeledExpr{ - pos: position{line: 1476, col: 54, offset: 44738}, + pos: position{line: 1476, col: 54, offset: 44736}, label: "comment", expr: &ruleRefExpr{ - pos: position{line: 1476, col: 63, offset: 44747}, + pos: position{line: 1476, col: 63, offset: 44745}, offset: 243, }, }, &andExpr{ - pos: position{line: 1476, col: 89, offset: 44773}, + pos: position{line: 1476, col: 89, offset: 44771}, expr: &ruleRefExpr{ - pos: position{line: 1476, col: 90, offset: 44774}, + pos: position{line: 1476, col: 90, offset: 44772}, offset: 299, }, }, @@ -10236,12 +10236,12 @@ var g = &grammar{ }, { name: "SingleLineCommentDelimiter", - pos: position{line: 1480, col: 1, offset: 44864}, + pos: position{line: 1480, col: 1, offset: 44862}, expr: &actionExpr{ - pos: position{line: 1480, col: 30, offset: 44893}, + pos: position{line: 1480, col: 30, offset: 44891}, run: (*parser).callonSingleLineCommentDelimiter1, expr: &litMatcher{ - pos: position{line: 1480, col: 30, offset: 44893}, + pos: position{line: 1480, col: 30, offset: 44891}, val: "//", ignoreCase: false, want: "\"//\"", @@ -10250,16 +10250,16 @@ var g = &grammar{ }, { name: "SingleLineCommentContent", - pos: position{line: 1484, col: 1, offset: 44938}, + pos: position{line: 1484, col: 1, offset: 44936}, expr: &actionExpr{ - pos: position{line: 1484, col: 28, offset: 44965}, + pos: position{line: 1484, col: 28, offset: 44963}, run: (*parser).callonSingleLineCommentContent1, expr: &zeroOrMoreExpr{ - pos: position{line: 1484, col: 28, offset: 44965}, + pos: position{line: 1484, col: 28, offset: 44963}, expr: &charClassMatcher{ - pos: position{line: 1484, col: 28, offset: 44965}, - val: "[^\\n]", - chars: []rune{'\n'}, + pos: position{line: 1484, col: 28, offset: 44963}, + val: "[^\\r\\n]", + chars: []rune{'\r', '\n'}, ignoreCase: false, inverted: true, }, @@ -11864,12 +11864,6 @@ var g = &grammar{ }, &litMatcher{ pos: position{line: 1697, col: 19, offset: 52275}, - val: "\r", - ignoreCase: false, - want: "\"\\r\"", - }, - &litMatcher{ - pos: position{line: 1697, col: 26, offset: 52282}, val: "\r\n", ignoreCase: false, want: "\"\\r\\n\"", @@ -11880,23 +11874,23 @@ var g = &grammar{ }, { name: "Percentage", - pos: position{line: 1701, col: 1, offset: 52344}, + pos: position{line: 1701, col: 1, offset: 52335}, expr: &actionExpr{ - pos: position{line: 1701, col: 15, offset: 52358}, + pos: position{line: 1701, col: 15, offset: 52349}, run: (*parser).callonPercentage1, expr: &seqExpr{ - pos: position{line: 1701, col: 15, offset: 52358}, + pos: position{line: 1701, col: 15, offset: 52349}, exprs: []any{ &labeledExpr{ - pos: position{line: 1701, col: 15, offset: 52358}, + pos: position{line: 1701, col: 15, offset: 52349}, label: "percentage", expr: &ruleRefExpr{ - pos: position{line: 1701, col: 26, offset: 52369}, + pos: position{line: 1701, col: 26, offset: 52360}, offset: 289, }, }, &litMatcher{ - pos: position{line: 1701, col: 42, offset: 52385}, + pos: position{line: 1701, col: 42, offset: 52376}, val: "%", ignoreCase: false, want: "\"%\"", @@ -11907,17 +11901,17 @@ var g = &grammar{ }, { name: "PositiveInteger", - pos: position{line: 1705, col: 1, offset: 52421}, + pos: position{line: 1705, col: 1, offset: 52412}, expr: &actionExpr{ - pos: position{line: 1705, col: 20, offset: 52440}, + pos: position{line: 1705, col: 20, offset: 52431}, run: (*parser).callonPositiveInteger1, expr: &seqExpr{ - pos: position{line: 1705, col: 20, offset: 52440}, + pos: position{line: 1705, col: 20, offset: 52431}, exprs: []any{ &oneOrMoreExpr{ - pos: position{line: 1705, col: 21, offset: 52441}, + pos: position{line: 1705, col: 21, offset: 52432}, expr: &charClassMatcher{ - pos: position{line: 1705, col: 21, offset: 52441}, + pos: position{line: 1705, col: 21, offset: 52432}, val: "[0-9]", ranges: []rune{'0', '9'}, ignoreCase: false, @@ -11925,7 +11919,7 @@ var g = &grammar{ }, }, &andCodeExpr{ - pos: position{line: 1705, col: 29, offset: 52449}, + pos: position{line: 1705, col: 29, offset: 52440}, run: (*parser).callonPositiveInteger5, }, }, @@ -11934,31 +11928,31 @@ var g = &grammar{ }, { name: "NotNewLine", - pos: position{line: 1711, col: 1, offset: 52540}, + pos: position{line: 1711, col: 1, offset: 52531}, expr: ¬Expr{ - pos: position{line: 1711, col: 14, offset: 52553}, + pos: position{line: 1711, col: 14, offset: 52544}, expr: &ruleRefExpr{ - pos: position{line: 1711, col: 15, offset: 52554}, + pos: position{line: 1711, col: 15, offset: 52545}, offset: 287, }, }, }, { name: "NotSpace", - pos: position{line: 1713, col: 1, offset: 52563}, + pos: position{line: 1713, col: 1, offset: 52554}, expr: ¬Expr{ - pos: position{line: 1713, col: 12, offset: 52574}, + pos: position{line: 1713, col: 12, offset: 52565}, expr: &ruleRefExpr{ - pos: position{line: 1713, col: 13, offset: 52575}, + pos: position{line: 1713, col: 13, offset: 52566}, offset: 308, }, }, }, { name: "Alpha", - pos: position{line: 1715, col: 1, offset: 52578}, + pos: position{line: 1715, col: 1, offset: 52569}, expr: &charClassMatcher{ - pos: position{line: 1715, col: 9, offset: 52586}, + pos: position{line: 1715, col: 9, offset: 52577}, val: "[\\p{L}]", classes: []*unicode.RangeTable{rangeTable("L")}, ignoreCase: false, @@ -11967,9 +11961,9 @@ var g = &grammar{ }, { name: "Alphanumeric", - pos: position{line: 1717, col: 1, offset: 52595}, + pos: position{line: 1717, col: 1, offset: 52586}, expr: &charClassMatcher{ - pos: position{line: 1717, col: 16, offset: 52610}, + pos: position{line: 1717, col: 16, offset: 52601}, val: "[\\p{L}\\p{N}]", classes: []*unicode.RangeTable{rangeTable("L"), rangeTable("N")}, ignoreCase: false, @@ -11978,46 +11972,46 @@ var g = &grammar{ }, { name: "EndOfFile", - pos: position{line: 1719, col: 1, offset: 52624}, + pos: position{line: 1719, col: 1, offset: 52615}, expr: ¬Expr{ - pos: position{line: 1719, col: 13, offset: 52636}, + pos: position{line: 1719, col: 13, offset: 52627}, expr: &anyMatcher{ - line: 1719, col: 14, offset: 52637, + line: 1719, col: 14, offset: 52628, }, }, }, { name: "NotEndOfFile", - pos: position{line: 1721, col: 1, offset: 52641}, + pos: position{line: 1721, col: 1, offset: 52632}, expr: &andExpr{ - pos: position{line: 1721, col: 16, offset: 52656}, + pos: position{line: 1721, col: 16, offset: 52647}, expr: &anyMatcher{ - line: 1721, col: 18, offset: 52658, + line: 1721, col: 18, offset: 52649, }, }, }, { name: "BeginningOfLine", - pos: position{line: 1723, col: 1, offset: 52662}, + pos: position{line: 1723, col: 1, offset: 52653}, expr: ¬Expr{ - pos: position{line: 1723, col: 19, offset: 52680}, + pos: position{line: 1723, col: 19, offset: 52671}, expr: &ruleRefExpr{ - pos: position{line: 1723, col: 20, offset: 52681}, + pos: position{line: 1723, col: 20, offset: 52672}, offset: 297, }, }, }, { name: "OffsetCharacter", - pos: position{line: 1725, col: 1, offset: 52698}, + pos: position{line: 1725, col: 1, offset: 52689}, expr: &seqExpr{ - pos: position{line: 1725, col: 19, offset: 52716}, + pos: position{line: 1725, col: 19, offset: 52707}, exprs: []any{ &anyMatcher{ - line: 1725, col: 19, offset: 52716, + line: 1725, col: 19, offset: 52707, }, &andCodeExpr{ - pos: position{line: 1725, col: 21, offset: 52718}, + pos: position{line: 1725, col: 21, offset: 52709}, run: (*parser).callonOffsetCharacter3, }, }, @@ -12025,29 +12019,29 @@ var g = &grammar{ }, { name: "Indent", - pos: position{line: 1729, col: 1, offset: 52766}, + pos: position{line: 1729, col: 1, offset: 52757}, expr: &actionExpr{ - pos: position{line: 1729, col: 10, offset: 52775}, + pos: position{line: 1729, col: 10, offset: 52766}, run: (*parser).callonIndent1, expr: &seqExpr{ - pos: position{line: 1729, col: 10, offset: 52775}, + pos: position{line: 1729, col: 10, offset: 52766}, exprs: []any{ &ruleRefExpr{ - pos: position{line: 1729, col: 10, offset: 52775}, + pos: position{line: 1729, col: 10, offset: 52766}, offset: 303, }, &andExpr{ - pos: position{line: 1729, col: 20, offset: 52785}, + pos: position{line: 1729, col: 20, offset: 52776}, expr: ¬Expr{ - pos: position{line: 1729, col: 22, offset: 52787}, + pos: position{line: 1729, col: 22, offset: 52778}, expr: &ruleRefExpr{ - pos: position{line: 1729, col: 23, offset: 52788}, + pos: position{line: 1729, col: 23, offset: 52779}, offset: 308, }, }, }, &andCodeExpr{ - pos: position{line: 1729, col: 26, offset: 52791}, + pos: position{line: 1729, col: 26, offset: 52782}, run: (*parser).callonIndent7, }, }, @@ -12056,16 +12050,16 @@ var g = &grammar{ }, { name: "EndOfLine", - pos: position{line: 1735, col: 1, offset: 52860}, + pos: position{line: 1735, col: 1, offset: 52851}, expr: &choiceExpr{ - pos: position{line: 1735, col: 13, offset: 52872}, + pos: position{line: 1735, col: 13, offset: 52863}, alternatives: []any{ &ruleRefExpr{ - pos: position{line: 1735, col: 13, offset: 52872}, + pos: position{line: 1735, col: 13, offset: 52863}, offset: 287, }, &ruleRefExpr{ - pos: position{line: 1735, col: 23, offset: 52882}, + pos: position{line: 1735, col: 23, offset: 52873}, offset: 294, }, }, @@ -12073,17 +12067,17 @@ var g = &grammar{ }, { name: "InlineText", - pos: position{line: 1737, col: 1, offset: 52893}, + pos: position{line: 1737, col: 1, offset: 52884}, expr: &labeledExpr{ - pos: position{line: 1737, col: 14, offset: 52906}, + pos: position{line: 1737, col: 14, offset: 52897}, label: "text", expr: &actionExpr{ - pos: position{line: 1737, col: 20, offset: 52912}, + pos: position{line: 1737, col: 20, offset: 52903}, run: (*parser).callonInlineText2, expr: &oneOrMoreExpr{ - pos: position{line: 1737, col: 20, offset: 52912}, + pos: position{line: 1737, col: 20, offset: 52903}, expr: &ruleRefExpr{ - pos: position{line: 1737, col: 20, offset: 52912}, + pos: position{line: 1737, col: 20, offset: 52903}, offset: 293, }, }, @@ -12092,14 +12086,14 @@ var g = &grammar{ }, { name: "UnbrokenText", - pos: position{line: 1739, col: 1, offset: 52958}, + pos: position{line: 1739, col: 1, offset: 52949}, expr: &actionExpr{ - pos: position{line: 1739, col: 16, offset: 52973}, + pos: position{line: 1739, col: 16, offset: 52964}, run: (*parser).callonUnbrokenText1, expr: &oneOrMoreExpr{ - pos: position{line: 1739, col: 16, offset: 52973}, + pos: position{line: 1739, col: 16, offset: 52964}, expr: &ruleRefExpr{ - pos: position{line: 1739, col: 16, offset: 52973}, + pos: position{line: 1739, col: 16, offset: 52964}, offset: 305, }, }, @@ -12107,13 +12101,13 @@ var g = &grammar{ }, { name: "AnyText", - pos: position{line: 1743, col: 1, offset: 53035}, + pos: position{line: 1743, col: 1, offset: 53026}, expr: &zeroOrMoreExpr{ - pos: position{line: 1743, col: 11, offset: 53045}, + pos: position{line: 1743, col: 11, offset: 53036}, expr: &charClassMatcher{ - pos: position{line: 1743, col: 11, offset: 53045}, - val: "[^\\n]", - chars: []rune{'\n'}, + pos: position{line: 1743, col: 11, offset: 53036}, + val: "[^\\r\\n]", + chars: []rune{'\r', '\n'}, ignoreCase: false, inverted: true, }, @@ -12121,11 +12115,11 @@ var g = &grammar{ }, { name: "AnySpaces", - pos: position{line: 1745, col: 1, offset: 53054}, + pos: position{line: 1745, col: 1, offset: 53047}, expr: &zeroOrMoreExpr{ - pos: position{line: 1745, col: 13, offset: 53066}, + pos: position{line: 1745, col: 13, offset: 53059}, expr: &charClassMatcher{ - pos: position{line: 1745, col: 13, offset: 53066}, + pos: position{line: 1745, col: 13, offset: 53059}, val: "[ \\t]", chars: []rune{' ', '\t'}, ignoreCase: false, @@ -12135,18 +12129,18 @@ var g = &grammar{ }, { name: "CharacterClassAll", - pos: position{line: 1747, col: 1, offset: 53075}, + pos: position{line: 1747, col: 1, offset: 53068}, expr: &actionExpr{ - pos: position{line: 1747, col: 21, offset: 53095}, + pos: position{line: 1747, col: 21, offset: 53088}, run: (*parser).callonCharacterClassAll1, expr: &choiceExpr{ - pos: position{line: 1747, col: 22, offset: 53096}, + pos: position{line: 1747, col: 22, offset: 53089}, alternatives: []any{ &anyMatcher{ - line: 1747, col: 22, offset: 53096, + line: 1747, col: 22, offset: 53089, }, &ruleRefExpr{ - pos: position{line: 1747, col: 26, offset: 53100}, + pos: position{line: 1747, col: 26, offset: 53093}, offset: 287, }, }, @@ -12155,20 +12149,20 @@ var g = &grammar{ }, { name: "CharacterClassInline", - pos: position{line: 1751, col: 1, offset: 53149}, + pos: position{line: 1751, col: 1, offset: 53142}, expr: &charClassMatcher{ - pos: position{line: 1751, col: 24, offset: 53172}, - val: "[^\\n]", - chars: []rune{'\n'}, + pos: position{line: 1751, col: 24, offset: 53165}, + val: "[^\\r\\n]", + chars: []rune{'\r', '\n'}, ignoreCase: false, inverted: true, }, }, { name: "CharacterGroupWord", - pos: position{line: 1753, col: 1, offset: 53180}, + pos: position{line: 1753, col: 1, offset: 53175}, expr: &charClassMatcher{ - pos: position{line: 1753, col: 22, offset: 53201}, + pos: position{line: 1753, col: 22, offset: 53196}, val: "[\\p{M}\\p{Nd}\\p{Pc}\\p{Join_Control}\\p{L}]", classes: []*unicode.RangeTable{rangeTable("M"), rangeTable("Nd"), rangeTable("Pc"), rangeTable("Join_Control"), rangeTable("L")}, ignoreCase: false, @@ -12177,14 +12171,14 @@ var g = &grammar{ }, { name: "__", - pos: position{line: 1755, col: 1, offset: 53244}, + pos: position{line: 1755, col: 1, offset: 53239}, expr: &actionExpr{ - pos: position{line: 1755, col: 6, offset: 53249}, + pos: position{line: 1755, col: 6, offset: 53244}, run: (*parser).callon__1, expr: &oneOrMoreExpr{ - pos: position{line: 1755, col: 6, offset: 53249}, + pos: position{line: 1755, col: 6, offset: 53244}, expr: &charClassMatcher{ - pos: position{line: 1755, col: 6, offset: 53249}, + pos: position{line: 1755, col: 6, offset: 53244}, val: "[ \\t]", chars: []rune{' ', '\t'}, ignoreCase: false, @@ -12195,12 +12189,12 @@ var g = &grammar{ }, { name: "_", - pos: position{line: 1759, col: 1, offset: 53296}, + pos: position{line: 1759, col: 1, offset: 53291}, expr: &actionExpr{ - pos: position{line: 1759, col: 5, offset: 53300}, + pos: position{line: 1759, col: 5, offset: 53295}, run: (*parser).callon_1, expr: &charClassMatcher{ - pos: position{line: 1759, col: 5, offset: 53300}, + pos: position{line: 1759, col: 5, offset: 53295}, val: "[ \\t]", chars: []rune{' ', '\t'}, ignoreCase: false, @@ -12210,9 +12204,9 @@ var g = &grammar{ }, { name: "Escape", - pos: position{line: 1763, col: 1, offset: 53346}, + pos: position{line: 1763, col: 1, offset: 53341}, expr: &litMatcher{ - pos: position{line: 1763, col: 10, offset: 53355}, + pos: position{line: 1763, col: 10, offset: 53350}, val: "\\", ignoreCase: false, want: "\"\\\\\"", @@ -12220,9 +12214,9 @@ var g = &grammar{ }, { name: "DoubleEscape", - pos: position{line: 1765, col: 1, offset: 53362}, + pos: position{line: 1765, col: 1, offset: 53357}, expr: &litMatcher{ - pos: position{line: 1765, col: 16, offset: 53377}, + pos: position{line: 1765, col: 16, offset: 53372}, val: "\\\\", ignoreCase: false, want: "\"\\\\\\\\\"", diff --git a/asciidoc/parse/current.go b/asciidoc/parse/current.go index 39f45e73..f557c423 100644 --- a/asciidoc/parse/current.go +++ b/asciidoc/parse/current.go @@ -30,10 +30,6 @@ func (c *current) previousRuneIsWhitespace() bool { return unicode.IsSpace(r) } -func (c *current) currentColumn() int { - return c.pos.col -} - func (c *current) currentPosition() (line, col, offset int) { line = c.parser.offset.line + c.pos.line col = c.pos.col diff --git a/asciidoc/parse/current_column.go b/asciidoc/parse/current_column.go new file mode 100644 index 00000000..0ab2bda2 --- /dev/null +++ b/asciidoc/parse/current_column.go @@ -0,0 +1,7 @@ +//go:build !windows + +package parse + +func (c *current) currentColumn() int { + return c.pos.col +} diff --git a/asciidoc/parse/current_column_windows.go b/asciidoc/parse/current_column_windows.go new file mode 100644 index 00000000..a8fac808 --- /dev/null +++ b/asciidoc/parse/current_column_windows.go @@ -0,0 +1,11 @@ +//go:build windows + +package parse + +func (c *current) currentColumn() int { + if len(c.text) == 2 && c.text[0] == '\r' && c.text[1] == '\n' { + // Special case on Windows; NewLine matches \r\n, but the parser only sees \n as a col reset, so we fake it + return 0 + } + return c.pos.col +} diff --git a/asciidoc/parse/grammar/comment.peg b/asciidoc/parse/grammar/comment.peg index 00200404..f686d031 100644 --- a/asciidoc/parse/grammar/comment.peg +++ b/asciidoc/parse/grammar/comment.peg @@ -8,7 +8,7 @@ SingleLineCommentDelimiter = "//" { return string(c.text), nil } -SingleLineCommentContent = [^\n]* { +SingleLineCommentContent = [^\r\n]* { debugPosition(c, "matched comment content\n") return string(c.text), nil } diff --git a/asciidoc/parse/grammar/path.peg b/asciidoc/parse/grammar/path.peg index 42050953..3d10f6a1 100644 --- a/asciidoc/parse/grammar/path.peg +++ b/asciidoc/parse/grammar/path.peg @@ -4,7 +4,7 @@ Path = !"[" elements:( PathElement+) { return mergeStrings(elements.([]any)), nil } -PathElement = (([^\r\n{.<>!?,;[\] ])+ +PathElement = (([^\n{.<>!?,;[\] ])+ / ([.?!;,] &(!_ !EndOfFile)) / AttributeReference) diff --git a/asciidoc/parse/grammar/text.peg b/asciidoc/parse/grammar/text.peg index 1dedeefc..628ddce6 100644 --- a/asciidoc/parse/grammar/text.peg +++ b/asciidoc/parse/grammar/text.peg @@ -38,7 +38,7 @@ EmptyLine = BeginningOfLine AnySpaces NewLine { SoftNewLine = !"\\" NewLine -NewLine = ("\n" / "\r" / "\r\n" ) { +NewLine = ("\n" / "\r\n") { debugPosition(c, "newline\n") return compose(c, &asciidoc.NewLine{}) } @@ -89,7 +89,7 @@ UnbrokenText = CharacterClassInline+ { return string(c.text), nil } -AnyText = [^\n]* +AnyText = [^\r\n]* AnySpaces = [ \t]* @@ -98,7 +98,7 @@ CharacterClassAll = (. / NewLine) { return string(c.text), nil } -CharacterClassInline = [^\n] +CharacterClassInline = [^\r\n] CharacterGroupWord = [\p{M}\p{Nd}\p{Pc}\p{Join_Control}\p{L}] diff --git a/cmd/cli.go b/cmd/cli.go index 87f57e64..c9ec7ab1 100644 --- a/cmd/cli.go +++ b/cmd/cli.go @@ -10,6 +10,7 @@ import ( "github.com/project-chip/alchemy/cmd/format" "github.com/project-chip/alchemy/cmd/testplan" "github.com/project-chip/alchemy/cmd/validate" + "github.com/project-chip/alchemy/cmd/yaml2python" "github.com/project-chip/alchemy/cmd/zap" ) @@ -28,4 +29,5 @@ func init() { rootCmd.AddCommand(dm.Command) rootCmd.AddCommand(testplan.Command) rootCmd.AddCommand(validate.Command) + rootCmd.AddCommand(yaml2python.Command) } diff --git a/cmd/testplan/cmd.go b/cmd/testplan/cmd.go index 593a4983..a17f0c0a 100644 --- a/cmd/testplan/cmd.go +++ b/cmd/testplan/cmd.go @@ -26,6 +26,7 @@ var Command = &cobra.Command{ func init() { Command.Flags().String("specRoot", "connectedhomeip-spec", "the src root of your clone of CHIP-Specifications/connectedhomeip-spec") Command.Flags().String("testRoot", "chip-test-plans", "the root of your clone of CHIP-Specifications/chip-test-plans") + Command.Flags().String("templateRoot", "", "the root of your local template files; if not specified, Alchemy will use an internal copy") Command.Flags().Bool("overwrite", false, "overwrite existing test plans") } @@ -36,10 +37,16 @@ func tp(cmd *cobra.Command, args []string) (err error) { specRoot, _ := cmd.Flags().GetString("specRoot") testRoot, _ := cmd.Flags().GetString("testRoot") overwrite, _ := cmd.Flags().GetBool("overwrite") + templateRoot, _ := cmd.Flags().GetString("templateRoot") asciiSettings := common.ASCIIDocAttributes(cmd) fileOptions := files.Flags(cmd) pipelineOptions := pipeline.Flags(cmd) + var testplanGeneratorOptions []testplan.GeneratorOption + + if templateRoot != "" { + testplanGeneratorOptions = append(testplanGeneratorOptions, testplan.TemplateRoot(templateRoot)) + } errata.LoadErrataConfig(specRoot) @@ -91,7 +98,7 @@ func tp(cmd *cobra.Command, args []string) (err error) { } } - generator := testplan.NewGenerator(testRoot, overwrite) + generator := testplan.NewGenerator(testRoot, overwrite, testplanGeneratorOptions...) var testplans pipeline.Map[string, *pipeline.Data[string]] testplans, err = pipeline.Process[*spec.Doc, string](cxt, pipelineOptions, generator, specDocs) if err != nil { diff --git a/cmd/yaml2python/cmd.go b/cmd/yaml2python/cmd.go new file mode 100644 index 00000000..a4a222dc --- /dev/null +++ b/cmd/yaml2python/cmd.go @@ -0,0 +1,122 @@ +package yaml2python + +import ( + "context" + "path/filepath" + + "github.com/project-chip/alchemy/cmd/common" + "github.com/project-chip/alchemy/errata" + "github.com/project-chip/alchemy/internal/files" + "github.com/project-chip/alchemy/internal/pipeline" + "github.com/project-chip/alchemy/matter/spec" + "github.com/project-chip/alchemy/testing/generate" + "github.com/project-chip/alchemy/testing/parse" + "github.com/spf13/cobra" +) + +var Command = &cobra.Command{ + Use: "yaml2python", + Short: "create a shell python script from a test YAML", + RunE: tp, +} + +func init() { + Command.Flags().String("specRoot", "connectedhomeip-spec", "the src root of your clone of CHIP-Specifications/connectedhomeip-spec") + Command.Flags().String("sdkRoot", "connectedhomeip", "the root of your clone of project-chip/connectedhomeip") + Command.Flags().String("templateRoot", "", "the root of your local template files; if not specified, Alchemy will use an internal copy") + Command.Flags().Bool("overwrite", true, "overwrite existing test scripts") +} + +func tp(cmd *cobra.Command, args []string) (err error) { + + cxt := context.Background() + + specRoot, _ := cmd.Flags().GetString("specRoot") + sdkRoot, _ := cmd.Flags().GetString("sdkRoot") + + asciiSettings := common.ASCIIDocAttributes(cmd) + fileOptions := files.Flags(cmd) + pipelineOptions := pipeline.Flags(cmd) + + overwrite, _ := cmd.Flags().GetBool("overwrite") + templateRoot, _ := cmd.Flags().GetString("templateRoot") + generatorOptions := []generate.GeneratorOption{ + generate.Overwrite(overwrite), + generate.TemplateRoot(templateRoot), + } + + var inputs pipeline.Map[string, *pipeline.Data[struct{}]] + inputs, err = pipeline.Start[struct{}](cxt, files.PathsTargeter(args...)) + if err != nil { + return err + } + + inputs, err = pipeline.ProcessCollectiveFunc(cxt, inputs, "Filtering YAML tests", func(cxt context.Context, inputs []*pipeline.Data[struct{}]) (outputs []*pipeline.Data[struct{}], err error) { + for _, input := range inputs { + switch filepath.Base(input.Path) { + case "PICS.yaml": + default: + outputs = append(outputs, input) + } + } + return + }) + if err != nil { + return err + } + + errata.LoadErrataConfig(specRoot) + + var parser parse.TestYamlParser + parser, err = parse.NewTestYamlParser(sdkRoot) + if err != nil { + return err + } + + var tests pipeline.Map[string, *pipeline.Data[*parse.Test]] + tests, err = pipeline.Process[struct{}, *parse.Test](cxt, pipelineOptions, parser, inputs) + if err != nil { + return err + } + + docParser, err := spec.NewParser(specRoot, asciiSettings) + if err != nil { + return err + } + + specFiles, err := pipeline.Start[struct{}](cxt, spec.Targeter(specRoot)) + if err != nil { + return err + } + + specDocs, err := pipeline.Process[struct{}, *spec.Doc](cxt, pipelineOptions, docParser, specFiles) + if err != nil { + return err + } + + specBuilder := spec.NewBuilder() + _, err = pipeline.Process[*spec.Doc, *spec.Doc](cxt, pipelineOptions, &specBuilder, specDocs) + if err != nil { + return err + } + + picsLabels, err := parse.LoadPICSLabels(sdkRoot) + if err != nil { + return err + } + + generator := generate.NewPythonTestGenerator(specBuilder.Spec, sdkRoot, picsLabels, generatorOptions...) + var scripts pipeline.Map[string, *pipeline.Data[string]] + scripts, err = pipeline.Process[*parse.Test, string](cxt, pipelineOptions, generator, tests) + if err != nil { + return err + } + + writer := files.NewWriter[string]("Writing test scripts", fileOptions) + _, err = pipeline.Process[string, struct{}](cxt, pipelineOptions, writer, scripts) + if err != nil { + return err + } + + return +} diff --git a/cmd/zap/cmd.go b/cmd/zap/cmd.go index a33840bf..741aecf8 100644 --- a/cmd/zap/cmd.go +++ b/cmd/zap/cmd.go @@ -128,7 +128,7 @@ func zapTemplates(cmd *cobra.Command, args []string) (err error) { var patchedDeviceTypes pipeline.Map[string, *pipeline.Data[[]byte]] if deviceTypes.Size() > 0 { - deviceTypePatcher := generate.NewDeviceTypesPatcher(sdkRoot, specBuilder.Spec, clusterAliases) + deviceTypePatcher := generate.NewDeviceTypesPatcher(sdkRoot, specBuilder.Spec, clusterAliases, generate.DeviceTypePatcherGenerateFeatureXML(featureXML)) patchedDeviceTypes, err = pipeline.Process[[]*matter.DeviceType, []byte](cxt, pipelineOptions, deviceTypePatcher, deviceTypes) if err != nil { return err @@ -256,8 +256,7 @@ func patchSpec(spec *spec.Specification) { } for _, s := range label.Structs { if s.Name == "LabelStruct" { - s.ParentEntity = fixedLabel - fixedLabel.Structs = append(fixedLabel.Structs, s) + fixedLabel.MoveStruct(s) break } } diff --git a/dm/conformance.go b/dm/conformance.go index 80e9a710..ae42861c 100644 --- a/dm/conformance.go +++ b/dm/conformance.go @@ -53,25 +53,7 @@ func renderConformance(doc *spec.Doc, identifierStore conformance.IdentifierStor parent.CreateElement("provisionalConform") case *conformance.Optional: oc := parent.CreateElement("optionalConform") - if con.Choice != nil { - oc.CreateAttr("choice", con.Choice.Set) - if con.Choice.Limit != nil { - switch l := con.Choice.Limit.(type) { - case *conformance.ChoiceExactLimit: - oc.CreateAttr("min", strconv.Itoa(l.Limit)) - oc.CreateAttr("max", strconv.Itoa(l.Limit)) - case *conformance.ChoiceMinLimit: - oc.CreateAttr("more", "true") // Existing data model does this for some reason - oc.CreateAttr("min", strconv.Itoa(l.Min)) - case *conformance.ChoiceMaxLimit: - oc.CreateAttr("max", strconv.Itoa(l.Max)) - case *conformance.ChoiceRangeLimit: - oc.CreateAttr("more", "true") // Existing data model does this for some reason - oc.CreateAttr("min", strconv.Itoa(l.Min)) - oc.CreateAttr("max", strconv.Itoa(l.Max)) - } - } - } + renderChoice(con.Choice, oc) return renderConformanceExpression(doc, identifierStore, con.Expression, oc) case *conformance.Disallowed: parent.CreateElement("disallowConform") @@ -92,6 +74,30 @@ func renderConformance(doc *spec.Doc, identifierStore conformance.IdentifierStor return nil } +func renderChoice(choice *conformance.Choice, parent *etree.Element) { + if choice == nil { + return + } + parent.CreateAttr("choice", choice.Set) + if choice.Limit == nil { + return + } + switch l := choice.Limit.(type) { + case *conformance.ChoiceExactLimit: + parent.CreateAttr("min", strconv.Itoa(l.Limit)) + parent.CreateAttr("max", strconv.Itoa(l.Limit)) + case *conformance.ChoiceMinLimit: + parent.CreateAttr("more", "true") // Existing data model does this for some reason + parent.CreateAttr("min", strconv.Itoa(l.Min)) + case *conformance.ChoiceMaxLimit: + parent.CreateAttr("max", strconv.Itoa(l.Max)) + case *conformance.ChoiceRangeLimit: + parent.CreateAttr("more", "true") // Existing data model does this for some reason + parent.CreateAttr("min", strconv.Itoa(l.Min)) + parent.CreateAttr("max", strconv.Itoa(l.Max)) + } +} + func renderConformanceExpression(doc *spec.Doc, identifierStore conformance.IdentifierStore, exp conformance.Expression, parent *etree.Element) error { if exp == nil { return nil diff --git a/errata/config.go b/errata/config.go index 211bd38f..53573dfe 100644 --- a/errata/config.go +++ b/errata/config.go @@ -1,6 +1,7 @@ package errata import ( + _ "embed" "log/slog" "os" "path/filepath" @@ -9,6 +10,9 @@ import ( "github.com/project-chip/alchemy/internal/files" ) +//go:embed default.yaml +var defaultErrata []byte + func LoadErrataConfig(specRoot string) { if specRoot == "" { return @@ -19,31 +23,33 @@ func LoadErrataConfig(specRoot string) { slog.Warn("error checking for errata path", slog.Any("error", err)) return } + var b []byte if !exists { - slog.Warn("errata file does not exist", slog.Any("path", errataPath)) - for p := range Erratas { - path := filepath.Join(specRoot, p) - exists, _ := files.Exists(path) - if !exists { - slog.Warn("errata points to non-existent file", "path", p) - } - } + slog.Warn("errata file does not exist; using embedded errata", slog.Any("path", errataPath)) + b = defaultErrata } else { - b, err := os.ReadFile(errataPath) + var err error + b, err = os.ReadFile(errataPath) + slog.Debug("Using errata overlay", slog.Any("path", errataPath)) if err != nil { slog.Warn("error reading errata file", slog.Any("error", err)) return } - var errataOverlay errataOverlay - err = yaml.Unmarshal(b, &errataOverlay) - if err != nil { - slog.Warn("error parsing errata file", slog.Any("error", err)) - return + } + var errataOverlay errataOverlay + err = yaml.Unmarshal(b, &errataOverlay) + if err != nil { + slog.Warn("error parsing errata file", slog.Any("error", err)) + return + } + Erratas = errataOverlay.Errata + for p := range Erratas { + path := filepath.Join(specRoot, p) + exists, _ := files.Exists(path) + if !exists { + slog.Warn("errata points to non-existent file", "path", p) } - slog.Debug("Using errata overlay", slog.Any("path", errataPath), slog.Any("count", len(errataOverlay.Errata))) - Erratas = errataOverlay.Errata } - } type errataOverlay struct { diff --git a/errata/default.yaml b/errata/default.yaml new file mode 100644 index 00000000..76c977c5 --- /dev/null +++ b/errata/default.yaml @@ -0,0 +1,601 @@ +errata: + src/app_clusters/AirQuality.adoc: + zap: + suppress-cluster-define-prefix: true + src/app_clusters/AlarmBase.adoc: + zap: + skip-file: true + src/app_clusters/BallastConfiguration.adoc: + zap: + suppress-cluster-define-prefix: true + src/app_clusters/BooleanState.adoc: + test-plan: + testplan-path: src/cluster/booleanstate.adoc + zap: + suppress-cluster-define-prefix: true + src/app_clusters/ColorControl.adoc: + test-plan: + testplan-path: src/cluster/colorcontrol.adoc + zap: + cluster-define-prefix: COLOR_CONTROL_ + src/app_clusters/ConcentrationMeasurement.adoc: + zap: + suppress-cluster-define-prefix: true + test-plan: + testplan-paths: + Carbon Dioxide Concentration Measurement: + path: src/cluster/concentration-measurement/carbon_dioxide_concentration_measurement_cluster.adoc + Carbon Monoxide Concentration Measurement: + path: src/cluster/concentration-measurement/carbon_monoxide_concentration_measurement_cluster.adoc + Formaldehyde Concentration Measurement: + path: src/cluster/concentration-measurement/formaldehyde_concentration_measurement_cluster.adoc + Nitrogen Dioxide Concentration Measurement: + path: src/cluster/concentration-measurement/nitrogen_dioxide_concentration_measurement_cluster.adoc + Ozone Concentration Measurement: + path: src/cluster/concentration-measurement/ozone_concentration_measurement_cluster.adoc + PM10 Concentration Measurement: + path: src/cluster/concentration-measurement/pm10_concentration_measurement_cluster.adoc + PM1 Concentration Measurement: + path: src/cluster/concentration-measurement/pm1_concentration_measurement_cluster.adoc + PM2.5 Concentration Measurement: + path: src/cluster/concentration-measurement/pm2_5_concentration_measurement_cluster.adoc + Radon Concentration Measurement: + path: src/cluster/concentration-measurement/radon_concentration_measurement_cluster.adoc + Total Volatile Organic Compounds Concentration Measurement: + path: src/cluster/concentration-measurement/total_volatile_organic_compounds_concentration_measurement_cluster.adoc + src/app_clusters/DemandResponseLoadControl.adoc: + zap: + override-defines: + ACTIVE_EVENTS: LOAD_CONTROL_ACTIVE_EVENTS + EVENTS: LOAD_CONTROL_EVENTS + cluster-aliases: + Demand Response Load Control: + - Demand Response and Load Control + template-path: drlc-cluster + src/app_clusters/DoorLock.adoc: + test-plan: + testplan-path: src/cluster/door_lock_Cluster.adoc + zap: + override-defines: + MAX_PIN_CODE_LENGTH: MAX_PIN_LENGTH + MIN_PIN_CODE_LENGTH: MIN_PIN_LENGTH + NUMBER_OF_CREDENTIALS_SUPPORTED_PER_USER: NUM_CREDENTIALS_SUPPORTED_PER_USER + NUMBER_OF_HOLIDAY_SCHEDULES_SUPPORTED: NUM_HOLIDAY_SCHEDULES_SUPPORTED + NUMBER_OF_PIN_USERS_SUPPORTED: NUM_PIN_USERS_SUPPORTED + NUMBER_OF_RFID_USERS_SUPPORTED: NUM_RFID_USERS_SUPPORTED + NUMBER_OF_TOTAL_USERS_SUPPORTED: NUM_TOTAL_USERS_SUPPORTED + NUMBER_OF_WEEK_DAY_SCHEDULES_SUPPORTED_PER_USER: NUM_WEEKDAY_SCHEDULES_SUPPORTED_PER_USER + NUMBER_OF_YEAR_DAY_SCHEDULES_SUPPORTED_PER_USER: NUM_YEARDAY_SCHEDULES_SUPPORTED_PER_USER + REQUIRE_PI_NFOR_REMOTE_OPERATION: REQUIRE_PIN_FOR_REMOTE_OPERATION + type-names: + ConfigurationRegisterBitmap: DlDefaultConfigurationRegister + CredentialRulesBitmap: DlCredentialRuleMask + DaysMaskBitmap: DaysMaskMap + LocalProgrammingFeaturesBitmap: DlLocalProgrammingFeatures + LockStateEnum: DlLockState + LockTypeEnum: DlLockType + OperatingModesBitmap: DlSupportedOperatingModes + src/app_clusters/EnergyEVSE.adoc: + zap: + suppress-cluster-define-prefix: true + template-path: energy-evse-cluster + src/app_clusters/FanControl.adoc: + test-plan: + testplan-path: src/cluster/FanControl.adoc + zap: + suppress-attribute-permissions: true + suppress-cluster-define-prefix: true + src/app_clusters/FlowMeasurement.adoc: + test-plan: + testplan-path: src/cluster/flowmeasurement.adoc + zap: + cluster-define-prefix: FLOW_ + src/app_clusters/Groups.adoc: + zap: + cluster-define-prefix: GROUP_ + src/app_clusters/IlluminanceMeasurement.adoc: + zap: + cluster-define-prefix: ILLUM_ + src/app_clusters/LaundryDryerControls.adoc: + test-plan: + testplan-path: src/cluster/LaundryDryerControls.adoc + src/app_clusters/LaundryWasherControls.adoc: + test-plan: + testplan-path: src/cluster/LaundryWasherControls.adoc + zap: + template-path: washer-controls-cluster + src/app_clusters/LevelControl.adoc: + test-plan: + testplan-path: src/cluster/levelcontrol.adoc + zap: + suppress-cluster-define-prefix: true + override-defines: + REMAINING_TIME: LEVEL_CONTROL_REMAINING_TIME + src/app_clusters/MicrowaveOvenControl.adoc: + test-plan: + testplan-path: src/cluster/MicrowaveOvenControl.adoc + zap: + suppress-cluster-define-prefix: true + src/app_clusters/ModeBase.adoc: + spec: + sections: + Mode Base Status CommonCodes Range: + skip: + - data-types-enum + zap: + cluster-skip: + - Mode Base + src/app_clusters/ModeBase_ModeTag_BaseValues.adoc: + spec: + utility-include: true + src/app_clusters/ModeSelect.adoc: + test-plan: + testplan-path: src/cluster/modeselect.adoc + zap: + override-defines: + DESCRIPTION: MODE_DESCRIPTION + src/app_clusters/Mode_DeviceEnergyManagement.adoc: + test-plan: + testplan-path: src/cluster/mode_device_energy_management.adoc + zap: + template-path: device-energy-management-mode-cluster + src/app_clusters/Mode_Dishwasher.adoc: + test-plan: + testplan-path: src/cluster/mode_dishwasher.adoc + zap: + template-path: dishwasher-mode-cluster + src/app_clusters/Mode_EVSE.adoc: + test-plan: + testplan-path: src/cluster/mode_energy_EVSE.adoc + zap: + template-path: energy-evse-mode-cluster + src/app_clusters/Mode_LaundryWasher.adoc: + test-plan: + testplan-path: src/cluster/mode_laundry_washer.adoc + zap: + template-path: laundry-washer-mode-cluster + src/app_clusters/Mode_MicrowaveOven.adoc: + test-plan: + testplan-path: src/cluster/mode_MicrowaveOven.adoc + zap: + template-path: microwave-oven-mode-cluster + src/app_clusters/Mode_Oven.adoc: + test-plan: + testplan-path: src/cluster/mode_oven.adoc + zap: + template-path: oven-mode-cluster + src/app_clusters/Mode_RVCClean.adoc: + test-plan: + testplan-path: src/cluster/mode_rvc_clean.adoc + zap: + template-path: rvc-clean-mode-cluster + src/app_clusters/Mode_RVCRun.adoc: + test-plan: + testplan-path: src/cluster/mode_rvc_run.adoc + zap: + template-path: rvc-run-mode-cluster + src/app_clusters/Mode_Refrigerator.adoc: + test-plan: + testplan-path: src/cluster/mode_ref_tcc.adoc + zap: + cluster-aliases: + Refrigerator And Temperature Controlled Cabinet Mode: + - Refrigerator and Temperature Controlled Cabinet Mode + template-path: refrigerator-and-temperature-controlled-cabinet-mode-cluster + src/app_clusters/Mode_WaterHeater.adoc: + test-plan: + testplan-path: src/cluster/mode_WaterHeater.adoc + zap: + template-path: water-heater-mode-cluster + src/app_clusters/OccupancySensing.adoc: + test-plan: + testplan-path: src/cluster/occupancysensing.adoc + src/app_clusters/OnOff.adoc: + test-plan: + testplan-path: src/cluster/onoff.adoc + zap: + cluster-aliases: + On/Off: + - OnOff + template-path: onoff-cluster + src/app_clusters/OperationalState.adoc: + spec: + sections: + ErrorStateEnum GeneralErrors Range: + skip: + - data-types-enum + Pause Command: + skip: + - command-arguments + Resume Command: + skip: + - command-arguments + test-plan: + testplan-path: src/cluster/operationalstate.adoc + src/app_clusters/OperationalState_ErrorStateEnum_BaseValues.adoc: + spec: + utility-include: true + src/app_clusters/OperationalState_OperationalStateEnum_BaseValues.adoc: + spec: + utility-include: true + src/app_clusters/OperationalState_Oven.adoc: + test-plan: + testplan-path: src/cluster/ovenoperationalstate.adoc + src/app_clusters/OperationalState_RVC.adoc: + test-plan: + testplan-path: src/cluster/rvcoperationalstate.adoc + zap: + template-path: operational-state-rvc-cluster + src/app_clusters/PressureMeasurement.adoc: + test-plan: + testplan-path: src/cluster/pressuremeasurement.adoc + zap: + cluster-define-prefix: PRESSURE_ + src/app_clusters/PumpConfigurationControl.adoc: + zap: + template-path: pump-configuration-and-control-cluster + src/app_clusters/RefrigeratorAlarm.adoc: + zap: + template-path: refrigerator-alarm + src/app_clusters/ResourceMonitoring.adoc: + zap: + separate-structs: + - ReplacementProductStruct + test-plan: + testplan-paths: + Activated Carbon Filter Monitoring: + path: src/cluster/resource-monitoring/activated_carbon_filter_monitoring.adoc + HEPA Filter Monitoring: + path: src/cluster/resource-monitoring/hepa_filter_monitoring.adoc + Water Tank Level Monitoring: + path: src/cluster/resource-monitoring/water_tank_level_monitoring.adoc + src/app_clusters/Scenes.adoc: + spec: + sections: + Form of ExtensionFieldSetStruct: + skip: + - data-types + - data-types-bitmap + - data-types-enum + - data-types-struct + Logical Scene Table: + skip: + - data-types + - data-types-bitmap + - data-types-enum + - data-types-struct + test-plan: + testplan-path: src/cluster/scenes.adoc + zap: + template-path: scene + src/app_clusters/SmokeCOAlarm.adoc: + test-plan: + testplan-path: src/cluster/smco.adoc + zap: + override-defines: + END_OF_SERVICE_ALERT: END_OF_SERVICEALERT + HARDWARE_FAULT_ALERT: HARDWARE_FAULTALERT + SMOKE_SENSITIVITY_LEVEL: SENSITIVITY_LEVEL + src/app_clusters/Switch.adoc: + zap: + domain: CHIP + src/app_clusters/TemperatureControl.adoc: + test-plan: + testplan-path: src/cluster/temperaturecontrol.adoc + zap: + override-defines: + MAX_TEMPERATURE: MAX_TEMP + MIN_TEMPERATURE: MIN_TEMP + SELECTED_TEMPERATURE_LEVEL: SELECTED_TEMP_LEVEL + SUPPORTED_TEMPERATURE_LEVELS: SUPPORTED_TEMP_LEVELS + TEMPERATURE_SETPOINT: TEMP_SETPOINT + src/app_clusters/TemperatureMeasurement.adoc: + test-plan: + testplan-path: src/cluster/temperaturemeasurement.adoc + zap: + cluster-define-prefix: TEMP_ + src/app_clusters/Thermostat.adoc: + zap: + suppress-cluster-define-prefix: true + override-defines: + OCCUPANCY: THERMOSTAT_OCCUPANCY + src/app_clusters/ThermostatUserInterfaceConfiguration.adoc: + test-plan: + testplan-path: src/cluster/thermostatuserconfiguration.adoc + zap: + suppress-cluster-define-prefix: true + src/app_clusters/ValveConfigurationControl.adoc: + zap: + template-path: valve-configuration-and-control-cluster + src/app_clusters/WaterContentMeasurement.adoc: + test-plan: + testplan-path: src/cluster/relativehumiditymeasurement.adoc + zap: + template-path: relative-humidity-measurement-cluster + src/app_clusters/WaterHeaterManagement.adoc: + test-plan: + testplan-path: src/cluster/WaterHeaterManagement.adoc + src/app_clusters/WiFiNetworkManagement.adoc: + test-plan: + testplan-path: src/cluster/wifi_network_management.adoc + zap: + template-path: wifi-network-management-cluster + src/app_clusters/WindowCovering.adoc: + zap: + cluster-define-prefix: WC_ + override-defines: + WC_CURRENT_POSITION_LIFT_PERCENT_100_THS: WC_CURRENT_POSITION_LIFT_PERCENT100THS + WC_CURRENT_POSITION_TILT_PERCENT_100_THS: WC_CURRENT_POSITION_TILT_PERCENT100THS + WC_TARGET_POSITION_LIFT_PERCENT_100_THS: WC_TARGET_POSITION_LIFT_PERCENT100THS + WC_TARGET_POSITION_TILT_PERCENT_100_THS: WC_TARGET_POSITION_TILT_PERCENT100THS + template-path: window-covering + src/app_clusters/meas_and_sense.adoc: + zap: + template-path: measurement-and-sensing + src/app_clusters/media/ApplicationBasic.adoc: + zap: + cluster-define-prefix: APPLICATION_ + override-defines: + APPLICATION_APPLICATION: APPLICATION_APP + src/app_clusters/media/ApplicationLauncher.adoc: + zap: + cluster-define-prefix: APPLICATION_LAUNCHER_ + override-defines: + APPLICATION_LAUNCHER_CATALOG_LIST: APPLICATION_LAUNCHER_LIST + src/app_clusters/media/AudioOutput.adoc: + zap: + cluster-define-prefix: AUDIO_OUTPUT_ + override-defines: + AUDIO_OUTPUT_OUTPUT_LIST: AUDIO_OUTPUT_LIST + src/app_clusters/media/Channel.adoc: + zap: + cluster-define-prefix: CHANNEL_ + src/app_clusters/media/ContentLauncher.adoc: + zap: + cluster-define-prefix: CONTENT_LAUNCHER_ + template-path: content-launch-cluster + src/app_clusters/media/KeypadInput.adoc: + zap: + cluster-define-prefix: ILLUM_ + src/app_clusters/media/MediaInput.adoc: + zap: + cluster-define-prefix: MEDIA_INPUT_ + override-defines: + MEDIA_INPUT_INPUT_LIST: MEDIA_INPUT_LIST + src/app_clusters/media/MediaPlayback.adoc: + zap: + cluster-define-prefix: MEDIA_PLAYBACK_ + override-defines: + MEDIA_PLAYBACK_CURRENT_STATE: MEDIA_PLAYBACK_STATE + MEDIA_PLAYBACK_SAMPLED_POSITION: MEDIA_PLAYBACK_PLAYBACK_POSITION + MEDIA_PLAYBACK_SEEK_RANGE_END: MEDIA_PLAYBACK_PLAYBACK_SEEK_RANGE_END + MEDIA_PLAYBACK_SEEK_RANGE_START: MEDIA_PLAYBACK_PLAYBACK_SEEK_RANGE_START + src/app_clusters/media/TargetNavigator.adoc: + zap: + cluster-define-prefix: TARGET_NAVIGATOR_ + override-defines: + TARGET_NAVIGATOR_TARGET_LIST: TARGET_NAVIGATOR_LIST + src/app_clusters/media/VideoPlayerArchitecture.adoc: + spec: + sections: + Video Player Architecture: + skip: + - cluster + src/app_clusters/media/WakeOnLAN.adoc: + zap: + override-defines: + MAC_ADDRESS: WAKE_ON_LAN_MAC_ADDRESS + cluster-aliases: + Wake on LAN: + - WakeOnLAN + src/data_model/ACL-Cluster.adoc: + test-plan: + testplan-path: src/cluster/AccessControl.adoc + zap: + cluster-aliases: + AccessControl: + - Access Control + template-path: access-control-cluster + src/data_model/Data-Model.adoc: + spec: + sections: + Choice Conformance: + skip: + - data-types-struct + Fabric-Scoped Struct: + skip: + - data-types-struct + Quality Conformance: + skip: + - data-types-struct + Struct Type: + skip: + - data-types-struct + src/data_model/Encoding-Specification.adoc: + spec: + sections: + Collection - Struct: + skip: + - data-types + - data-types-bitmap + - data-types-enum + - data-types-struct + Discrete - Bitmap: + skip: + - data-types + - data-types-bitmap + - data-types-enum + - data-types-struct + src/data_model/Group-Key-Management-Cluster.adoc: + test-plan: + testplan-path: src/group_communication.adoc + zap: + cluster-aliases: + GroupKeyManagement: + - Group Key Management + template-path: group-key-mgmt-cluster + src/data_model/ICDManagement.adoc: + test-plan: + testplan-path: src/cluster/icdmanagement.adoc + zap: + cluster-aliases: + ICDManagement: + - ICD Management + src/data_model/Label-Cluster.adoc: + zap: + template-path: user-label-cluster + cluster-split: + "0x0040": fixed-label-cluster + "0x0041": user-label-cluster + src/data_model/ValidProxies-Cluster.adoc: + zap: + template-path: proxy-valid-cluster + src/data_model/bridge-clusters.adoc: + zap: + cluster-split: + "0x0025": actions-cluster + "0x0039": bridged-device-basic-information + src/device_types/OtaRequestor.adoc: + disco: + sections: + ProviderLocation Type: + skip: + - data-type-rename + src/matter-defines.adoc: + spec: + utility-include: true + src/secure_channel/Discovery.adoc: + spec: + sections: + Common TXT Key/Value Pairs: + skip: + - data-types + - data-types-bitmap + - data-types-enum + - data-types-struct + TXT key for pairing hint (`PH`): + skip: + - data-types + - data-types-bitmap + - data-types-enum + - data-types-struct + src/service_device_management/AdminAssistedCommissioningFlows.adoc: + disco: + sections: + Basic Commissioning Method (BCM): + skip: + - normalize-anchor + Enhanced Commissioning Method (ECM): + skip: + - normalize-anchor + Open Commissioning Window: + skip: + - normalize-anchor + Presentation of Onboarding Payload for ECM: + skip: + - normalize-anchor + src/service_device_management/AdminCommissioningCluster.adoc: + test-plan: + testplan-path: src/multiplefabrics.adoc + zap: + template-path: administrator-commissioning-cluster + src/service_device_management/BasicInformationCluster.adoc: + zap: + domain: CHIP + src/service_device_management/DeviceCommissioningFlows.adoc: + spec: + sections: + Enhanced Setup Flow (ESF): + skip: + - data-types + - data-types-bitmap + - data-types-enum + - data-types-struct + src/service_device_management/DiagnosticLogsCluster.adoc: + test-plan: + testplan-path: src/cluster/logs_diagnostics.adoc + zap: + domain: CHIP + src/service_device_management/DiagnosticsEthernet.adoc: + test-plan: + testplan-path: src/cluster/ethernet_diagnostics.adoc + zap: + template-path: ethernet-network-diagnostics-cluster + src/service_device_management/DiagnosticsGeneral.adoc: + disco: + sections: + NetworkInterface Type: + skip: + - data-type-rename + zap: + template-path: general-diagnostics-cluster + src/service_device_management/DiagnosticsSoftware.adoc: + zap: + template-path: software-diagnostics-cluster + src/service_device_management/DiagnosticsThread.adoc: + disco: + sections: + OperationalDatasetComponents Type: + skip: + - data-type-rename + SecurityPolicy Type: + skip: + - data-type-rename + test-plan: + testplan-path: src/cluster/thread_nw_diagnostics.adoc + zap: + suppress-cluster-define-prefix: true + template-path: thread-network-diagnostics-cluster + src/service_device_management/DiagnosticsWiFi.adoc: + test-plan: + testplan-path: src/cluster/wifi_diagnostics.adoc + zap: + template-path: wifi-network-diagnostics-cluster + src/service_device_management/GeneralCommissioningCluster.adoc: + disco: + sections: + BasicCommissioningInfo Type: + skip: + - data-type-rename + src/service_device_management/LocalizationConfiguration.adoc: + test-plan: + testplan-path: src/cluster/Localization.adoc + src/service_device_management/LocalizationTimeFormat.adoc: + test-plan: + testplan-path: src/cluster/timeformatlocalization.adoc + zap: + template-path: time-format-localization-cluster + src/service_device_management/LocalizationUnit.adoc: + test-plan: + testplan-path: src/cluster/unitlocalization.adoc + zap: + template-path: unit-localization-cluster + domain: CHIP + src/service_device_management/OTAProvider.adoc: + zap: + skip-file: true + src/service_device_management/OTARequestor.adoc: + zap: + skip-file: true + src/service_device_management/OperationalCredentialCluster.adoc: + test-plan: + testplan-path: src/cluster/node_operational_credentials.adoc + zap: + cluster-name: Operational Credentials + cluster-aliases: + Operational Credentials: + - Node Operational Credentials + template-path: operational-credentials-cluster + src/service_device_management/PowerSourceCluster.adoc: + test-plan: + testplan-path: src/cluster/powersource.adoc + zap: + domain: CHIP + src/service_device_management/PowerSourceConfigurationCluster.adoc: + test-plan: + testplan-path: src/cluster/powersourceconfiguration.adoc + zap: + domain: CHIP + src/service_device_management/TimeSync.adoc: + zap: + template-path: time-synchronization-cluster diff --git a/errata/errata.go b/errata/errata.go index fc268e5b..670e75f5 100644 --- a/errata/errata.go +++ b/errata/errata.go @@ -1,9 +1,5 @@ package errata -import ( - "github.com/project-chip/alchemy/matter" -) - type Errata struct { Disco Disco `yaml:"disco,omitempty"` Spec Spec `yaml:"spec,omitempty"` @@ -21,637 +17,4 @@ func GetErrata(path string) *Errata { return DefaultErrata } -var Erratas = map[string]*Errata{ - "src/matter-defines.adoc": { - Spec: Spec{UtilityInclude: true}, - }, - "src/app_clusters/AirQuality.adoc": { - ZAP: ZAP{SuppressClusterDefinePrefix: true}, - }, - "src/app_clusters/AlarmBase.adoc": { - ZAP: ZAP{SkipFile: true}, - }, - "src/app_clusters/BallastConfiguration.adoc": { - ZAP: ZAP{SuppressClusterDefinePrefix: true}, - }, - "src/app_clusters/BooleanState.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/booleanstate.adoc", - }, - ZAP: ZAP{SuppressClusterDefinePrefix: true}, - }, - "src/app_clusters/ColorControl.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/colorcontrol.adoc", - }, - ZAP: ZAP{ClusterDefinePrefix: "COLOR_CONTROL_"}, - }, - "src/app_clusters/ConcentrationMeasurement.adoc": { - ZAP: ZAP{SuppressClusterDefinePrefix: true}, - }, - "src/app_clusters/DemandResponseLoadControl.adoc": { - ZAP: ZAP{ - TemplatePath: "drlc-cluster", - ClusterAliases: map[string][]string{ - "Demand Response Load Control": {"Demand Response and Load Control"}, - }, - DefineOverrides: map[string]string{ - "EVENTS": "LOAD_CONTROL_EVENTS", - "ACTIVE_EVENTS": "LOAD_CONTROL_ACTIVE_EVENTS", - }}, - }, - "src/app_clusters/DoorLock.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/door_lock_Cluster.adoc", - }, - ZAP: ZAP{ - DefineOverrides: map[string]string{ - "NUMBER_OF_TOTAL_USERS_SUPPORTED": "NUM_TOTAL_USERS_SUPPORTED", - "NUMBER_OF_PIN_USERS_SUPPORTED": "NUM_PIN_USERS_SUPPORTED", - "NUMBER_OF_RFID_USERS_SUPPORTED": "NUM_RFID_USERS_SUPPORTED", - "NUMBER_OF_WEEK_DAY_SCHEDULES_SUPPORTED_PER_USER": "NUM_WEEKDAY_SCHEDULES_SUPPORTED_PER_USER", - "NUMBER_OF_YEAR_DAY_SCHEDULES_SUPPORTED_PER_USER": "NUM_YEARDAY_SCHEDULES_SUPPORTED_PER_USER", - "NUMBER_OF_HOLIDAY_SCHEDULES_SUPPORTED": "NUM_HOLIDAY_SCHEDULES_SUPPORTED", - "MAX_PIN_CODE_LENGTH": "MAX_PIN_LENGTH", - "MIN_PIN_CODE_LENGTH": "MIN_PIN_LENGTH", - "NUMBER_OF_CREDENTIALS_SUPPORTED_PER_USER": "NUM_CREDENTIALS_SUPPORTED_PER_USER", - "REQUIRE_PI_NFOR_REMOTE_OPERATION": "REQUIRE_PIN_FOR_REMOTE_OPERATION", - }, - TypeNames: map[string]string{ - "CredentialRulesBitmap": "DlCredentialRuleMask", - "DaysMaskBitmap": "DaysMaskMap", - "OperatingModesBitmap": "DlSupportedOperatingModes", - "ConfigurationRegisterBitmap": "DlDefaultConfigurationRegister", - "LocalProgrammingFeaturesBitmap": "DlLocalProgrammingFeatures", - "LockStateEnum": "DlLockState", - "LockTypeEnum": "DlLockType", - }, - }, - }, - "src/app_clusters/EnergyEVSE.adoc": { - ZAP: ZAP{TemplatePath: "energy-evse-cluster", - SuppressClusterDefinePrefix: true}, - }, - "src/app_clusters/FanControl.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/FanControl.adoc", - }, - ZAP: ZAP{SuppressAttributePermissions: true, - SuppressClusterDefinePrefix: true}, - }, - "src/app_clusters/FlowMeasurement.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/flowmeasurement.adoc", - }, - ZAP: ZAP{ClusterDefinePrefix: "FLOW_"}, - }, - "src/app_clusters/Groups.adoc": { - ZAP: ZAP{ClusterDefinePrefix: "GROUP_"}, - }, - "src/app_clusters/IlluminanceMeasurement.adoc": { - ZAP: ZAP{ClusterDefinePrefix: "ILLUM_"}, - }, - "src/app_clusters/LaundryDryerControls.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/LaundryDryerControls.adoc", - }, - }, - "src/app_clusters/LaundryWasherControls.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/LaundryWasherControls.adoc", - }, - ZAP: ZAP{TemplatePath: "washer-controls-cluster"}, - }, - "src/app_clusters/LevelControl.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/levelcontrol.adoc", - }, - ZAP: ZAP{DefineOverrides: map[string]string{"REMAINING_TIME": "LEVEL_CONTROL_REMAINING_TIME"}, - SuppressClusterDefinePrefix: true}, - }, - "src/app_clusters/meas_and_sense.adoc": { - ZAP: ZAP{TemplatePath: "measurement-and-sensing"}, - }, - "src/app_clusters/MicrowaveOvenControl.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/MicrowaveOvenControl.adoc", - }, - ZAP: ZAP{SuppressClusterDefinePrefix: true}, - }, - "src/app_clusters/ModeBase.adoc": { - Spec: Spec{ - Sections: map[string]SpecSection{ - "Mode Base Status CommonCodes Range": {Skip: SpecPurposeDataTypesEnum}, - }, - }, - ZAP: ZAP{ - ClusterSkip: []string{"Mode Base"}, - }, - }, - "src/app_clusters/ModeBase_ModeTag_BaseValues.adoc": { - Spec: Spec{UtilityInclude: true}, - }, - "src/app_clusters/ModeSelect.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/modeselect.adoc", - }, - ZAP: ZAP{DefineOverrides: map[string]string{"DESCRIPTION": "MODE_DESCRIPTION"}}, - }, - "src/app_clusters/Mode_DeviceEnergyManagement.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/mode_device_energy_management.adoc", - }, - ZAP: ZAP{TemplatePath: "device-energy-management-mode-cluster"}, - }, - "src/app_clusters/Mode_Dishwasher.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/mode_dishwasher.adoc", - }, - ZAP: ZAP{TemplatePath: "dishwasher-mode-cluster"}, - }, - "src/app_clusters/Mode_EVSE.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/mode_energy_EVSE.adoc", - }, - ZAP: ZAP{TemplatePath: "energy-evse-mode-cluster"}, - }, - "src/app_clusters/Mode_LaundryWasher.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/mode_laundry_washer.adoc", - }, - ZAP: ZAP{TemplatePath: "laundry-washer-mode-cluster"}, - }, - "src/app_clusters/Mode_MicrowaveOven.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/mode_MicrowaveOven.adoc", - }, - ZAP: ZAP{TemplatePath: "microwave-oven-mode-cluster"}, - }, - "src/app_clusters/Mode_Oven.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/mode_oven.adoc", - }, - ZAP: ZAP{TemplatePath: "oven-mode-cluster"}, - }, - "src/app_clusters/Mode_Refrigerator.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/mode_ref_tcc.adoc", - }, - ZAP: ZAP{ - TemplatePath: "refrigerator-and-temperature-controlled-cabinet-mode-cluster", - ClusterAliases: map[string][]string{ - "Refrigerator And Temperature Controlled Cabinet Mode": {"Refrigerator and Temperature Controlled Cabinet Mode"}, - }, - }, - }, - "src/app_clusters/Mode_RVCClean.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/mode_rvc_clean.adoc", - }, - ZAP: ZAP{TemplatePath: "rvc-clean-mode-cluster"}, - }, - "src/app_clusters/Mode_RVCRun.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/mode_rvc_run.adoc", - }, - ZAP: ZAP{TemplatePath: "rvc-run-mode-cluster"}, - }, - "src/app_clusters/Mode_WaterHeater.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/mode_WaterHeater.adoc", - }, - ZAP: ZAP{TemplatePath: "water-heater-mode-cluster"}, - }, - "src/app_clusters/OccupancySensing.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/occupancysensing.adoc", - }, - }, - "src/app_clusters/OnOff.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/onoff.adoc", - }, - ZAP: ZAP{ - TemplatePath: "onoff-cluster", - ClusterAliases: map[string][]string{ - "On/Off": {"OnOff"}, - }, - }, - }, - "src/app_clusters/OperationalState.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/operationalstate.adoc", - }, - Spec: Spec{ - Sections: map[string]SpecSection{ - "ErrorStateEnum GeneralErrors Range": {Skip: SpecPurposeDataTypesEnum}, - "Resume Command": {Skip: SpecPurposeCommandArguments}, - "Pause Command": {Skip: SpecPurposeCommandArguments}, - }, - }, - }, - "src/app_clusters/OperationalState_ErrorStateEnum_BaseValues.adoc": { - Spec: Spec{UtilityInclude: true}, - }, - "src/app_clusters/OperationalState_OperationalStateEnum_BaseValues.adoc": { - Spec: Spec{UtilityInclude: true}, - }, - "src/app_clusters/OperationalState_Oven.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/ovenoperationalstate.adoc", - }, - }, - "src/app_clusters/OperationalState_RVC.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/rvcoperationalstate.adoc", - }, - ZAP: ZAP{TemplatePath: "operational-state-rvc-cluster"}, - }, - "src/app_clusters/PressureMeasurement.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/pressuremeasurement.adoc", - }, - ZAP: ZAP{ClusterDefinePrefix: "PRESSURE_"}, - }, - "src/app_clusters/PumpConfigurationControl.adoc": { - ZAP: ZAP{TemplatePath: "pump-configuration-and-control-cluster"}, - }, - "src/app_clusters/RefrigeratorAlarm.adoc": { - ZAP: ZAP{TemplatePath: "refrigerator-alarm"}, - }, - "src/app_clusters/ResourceMonitoring.adoc": { - ZAP: ZAP{SeparateStructs: map[string]struct{}{"ReplacementProductStruct": {}}}, - }, - "src/app_clusters/Scenes.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/scenes.adoc", - }, - Spec: Spec{ - Sections: map[string]SpecSection{ - "Form of ExtensionFieldSetStruct": {Skip: SpecPurposeDataTypes}, - "Logical Scene Table": {Skip: SpecPurposeDataTypes}, - }, - }, - ZAP: ZAP{TemplatePath: "scene"}, - }, - "src/app_clusters/SmokeCOAlarm.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/smco.adoc", - }, - ZAP: ZAP{DefineOverrides: map[string]string{ - "HARDWARE_FAULT_ALERT": "HARDWARE_FAULTALERT", - "END_OF_SERVICE_ALERT": "END_OF_SERVICEALERT", - "SMOKE_SENSITIVITY_LEVEL": "SENSITIVITY_LEVEL", - }}, - }, - "src/app_clusters/Switch.adoc": { - ZAP: ZAP{Domain: matter.DomainCHIP}, // wth? - }, - "src/app_clusters/TemperatureControl.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/temperaturecontrol.adoc", - }, - ZAP: ZAP{DefineOverrides: map[string]string{ - "TEMPERATURE_SETPOINT": "TEMP_SETPOINT", - "MIN_TEMPERATURE": "MIN_TEMP", - "MAX_TEMPERATURE": "MAX_TEMP", - "SELECTED_TEMPERATURE_LEVEL": "SELECTED_TEMP_LEVEL", - "SUPPORTED_TEMPERATURE_LEVELS": "SUPPORTED_TEMP_LEVELS", - }}, - }, - "src/app_clusters/TemperatureMeasurement.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/temperaturemeasurement.adoc", - }, - ZAP: ZAP{ClusterDefinePrefix: "TEMP_"}, - }, - "src/app_clusters/Thermostat.adoc": { - ZAP: ZAP{SuppressClusterDefinePrefix: true, - DefineOverrides: map[string]string{ - "OCCUPANCY": "THERMOSTAT_OCCUPANCY", - }}, - }, - "src/app_clusters/ThermostatUserInterfaceConfiguration.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/thermostatuserconfiguration.adoc", - }, - ZAP: ZAP{SuppressClusterDefinePrefix: true}, - }, - "src/app_clusters/ValveConfigurationControl.adoc": { - ZAP: ZAP{TemplatePath: "valve-configuration-and-control-cluster"}, - }, - "src/app_clusters/WaterContentMeasurement.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/relativehumiditymeasurement.adoc", - }, - ZAP: ZAP{TemplatePath: "relative-humidity-measurement-cluster"}, - }, - "src/app_clusters/WaterHeaterManagement.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/WaterHeaterManagement.adoc", - }, - }, - "src/app_clusters/WebRTC_Provider.adoc": { - ZAP: ZAP{ - TemplatePath: "webrtc-provider-cluster", - DefineOverrides: map[string]string{ - "WEB_RTC_TRANSPORT_PROVIDER_CLUSTER": "WEBRTC_TRANSPORT_PROVIDER_CLUSTER", - }, - }, - }, - "src/app_clusters/WebRTC_Requestor.adoc": { - ZAP: ZAP{ - TemplatePath: "webrtc-requestor-cluster", - DefineOverrides: map[string]string{ - "WEB_RTC_TRANSPORT_REQUESTOR_CLUSTER": "WEBRTC_TRANSPORT_REQUESTOR_CLUSTER", - }, - }, - }, - "src/app_clusters/WiFiNetworkManagement.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/wifi_network_management.adoc", - }, - ZAP: ZAP{ - TemplatePath: "wifi-network-management-cluster", - }, - }, - "src/app_clusters/WindowCovering.adoc": { - ZAP: ZAP{TemplatePath: "window-covering", - ClusterDefinePrefix: "WC_", - DefineOverrides: map[string]string{ - "WC_TARGET_POSITION_LIFT_PERCENT_100_THS": "WC_TARGET_POSITION_LIFT_PERCENT100THS", - "WC_TARGET_POSITION_TILT_PERCENT_100_THS": "WC_TARGET_POSITION_TILT_PERCENT100THS", - "WC_CURRENT_POSITION_LIFT_PERCENT_100_THS": "WC_CURRENT_POSITION_LIFT_PERCENT100THS", - "WC_CURRENT_POSITION_TILT_PERCENT_100_THS": "WC_CURRENT_POSITION_TILT_PERCENT100THS", - }}, - }, - "src/app_clusters/media/ApplicationBasic.adoc": { - ZAP: ZAP{ClusterDefinePrefix: "APPLICATION_", - DefineOverrides: map[string]string{"APPLICATION_APPLICATION": "APPLICATION_APP"}}, - }, - "src/app_clusters/media/ApplicationLauncher.adoc": { - ZAP: ZAP{ClusterDefinePrefix: "APPLICATION_LAUNCHER_", - DefineOverrides: map[string]string{ - "APPLICATION_LAUNCHER_CATALOG_LIST": "APPLICATION_LAUNCHER_LIST", - }}, - }, - "src/app_clusters/media/AudioOutput.adoc": { - ZAP: ZAP{ClusterDefinePrefix: "AUDIO_OUTPUT_", - DefineOverrides: map[string]string{ - "AUDIO_OUTPUT_OUTPUT_LIST": "AUDIO_OUTPUT_LIST", - }}, - }, - "src/app_clusters/media/Channel.adoc": { - ZAP: ZAP{ClusterDefinePrefix: "CHANNEL_"}, - }, - "src/app_clusters/media/ContentLauncher.adoc": { - ZAP: ZAP{TemplatePath: "content-launch-cluster", - ClusterDefinePrefix: "CONTENT_LAUNCHER_"}, - }, - "src/app_clusters/media/KeypadInput.adoc": { - ZAP: ZAP{ClusterDefinePrefix: "ILLUM_"}, - }, - "src/app_clusters/media/MediaInput.adoc": { - ZAP: ZAP{ClusterDefinePrefix: "MEDIA_INPUT_", - DefineOverrides: map[string]string{"MEDIA_INPUT_INPUT_LIST": "MEDIA_INPUT_LIST"}}, - }, - "src/app_clusters/media/MediaPlayback.adoc": { - ZAP: ZAP{ClusterDefinePrefix: "MEDIA_PLAYBACK_", - DefineOverrides: map[string]string{ - "MEDIA_PLAYBACK_CURRENT_STATE": "MEDIA_PLAYBACK_STATE", - "MEDIA_PLAYBACK_SAMPLED_POSITION": "MEDIA_PLAYBACK_PLAYBACK_POSITION", - "MEDIA_PLAYBACK_SEEK_RANGE_END": "MEDIA_PLAYBACK_PLAYBACK_SEEK_RANGE_END", - "MEDIA_PLAYBACK_SEEK_RANGE_START": "MEDIA_PLAYBACK_PLAYBACK_SEEK_RANGE_START", - }}, - }, - "src/app_clusters/media/TargetNavigator.adoc": { - ZAP: ZAP{ClusterDefinePrefix: "TARGET_NAVIGATOR_", - DefineOverrides: map[string]string{ - "TARGET_NAVIGATOR_TARGET_LIST": "TARGET_NAVIGATOR_LIST", - }}, - }, - "src/app_clusters/media/WakeOnLAN.adoc": { - ZAP: ZAP{ - DefineOverrides: map[string]string{ - "MAC_ADDRESS": "WAKE_ON_LAN_MAC_ADDRESS", - }, - ClusterAliases: map[string][]string{ - "Wake on LAN": {"WakeOnLAN"}, - }, - }, - }, - "src/app_clusters/media/VideoPlayerArchitecture.adoc": { - Spec: Spec{ - Sections: map[string]SpecSection{ - "Video Player Architecture": {Skip: SpecPurposeCluster}, - }, - }, - }, - "src/data_model/ACL-Cluster.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/AccessControl.adoc", - }, - ZAP: ZAP{ - TemplatePath: "access-control-cluster", - ClusterAliases: map[string][]string{ - "AccessControl": {"Access Control"}, - }, - }, - }, - "src/data_model/bridge-clusters.adoc": { - ZAP: ZAP{ClusterSplit: map[string]string{ - "0x0025": "actions-cluster", - "0x0039": "bridged-device-basic-information", - }}, - }, - "src/data_model/Data-Model.adoc": { - Spec: Spec{ - Sections: map[string]SpecSection{ - "Struct Type": {Skip: SpecPurposeDataTypesStruct}, - "Quality Conformance": {Skip: SpecPurposeDataTypesStruct}, - "Choice Conformance": {Skip: SpecPurposeDataTypesStruct}, - "Fabric-Scoped Struct": {Skip: SpecPurposeDataTypesStruct}, - }, - }, - }, - "src/data_model/Encoding-Specification.adoc": { - Spec: Spec{ - Sections: map[string]SpecSection{ - "Discrete - Bitmap": {Skip: SpecPurposeDataTypes}, - "Collection - Struct": {Skip: SpecPurposeDataTypes}, - }, - }, - }, - "src/data_model/Group-Key-Management-Cluster.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/group_communication.adoc", - }, - ZAP: ZAP{ - TemplatePath: "group-key-mgmt-cluster", - ClusterAliases: map[string][]string{ - "GroupKeyManagement": {"Group Key Management"}, - }, - }, - }, - "src/data_model/ICDManagement.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/icdmanagement.adoc", - }, - ZAP: ZAP{ - ClusterAliases: map[string][]string{ - "ICDManagement": {"ICD Management"}, - }, - }, - }, - "src/data_model/Label-Cluster.adoc": { - ZAP: ZAP{TemplatePath: "user-label-cluster", - ClusterSplit: map[string]string{ - "0x0040": "fixed-label-cluster", - "0x0041": "user-label-cluster", - }}, - }, - "src/data_model/ValidProxies-Cluster.adoc": { - ZAP: ZAP{TemplatePath: "proxy-valid-cluster"}, - }, - "src/device_types/OtaRequestor.adoc": { - Disco: Disco{ - Sections: map[string]DiscoSection{ - "ProviderLocation Type": { - Skip: DiscoPurposeDataTypeRename, - }, - }, - }, - }, - "src/secure_channel/Discovery.adoc": { - Spec: Spec{ - Sections: map[string]SpecSection{ - "Common TXT Key/Value Pairs": {Skip: SpecPurposeDataTypes}, - "TXT key for pairing hint (`PH`)": {Skip: SpecPurposeDataTypes}, - }, - }, - }, - "src/service_device_management/AdminAssistedCommissioningFlows.adoc": { - Disco: Disco{ - Sections: map[string]DiscoSection{ - "Basic Commissioning Method (BCM)": {Skip: DiscoPurposeNormalizeAnchor}, - "Enhanced Commissioning Method (ECM)": {Skip: DiscoPurposeNormalizeAnchor}, - "Presentation of Onboarding Payload for ECM": {Skip: DiscoPurposeNormalizeAnchor}, - "Open Commissioning Window": {Skip: DiscoPurposeNormalizeAnchor}, - }, - }, - }, - "src/service_device_management/AdminCommissioningCluster.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/multiplefabrics.adoc", - }, - ZAP: ZAP{TemplatePath: "administrator-commissioning-cluster"}, - }, - "src/service_device_management/BasicInformationCluster.adoc": { - ZAP: ZAP{Domain: matter.DomainCHIP}, - }, - "src/service_device_management/DeviceCommissioningFlows.adoc": { - Spec: Spec{ - Sections: map[string]SpecSection{"Enhanced Setup Flow (ESF)": {Skip: SpecPurposeDataTypes}}, - }, - }, - "src/service_device_management/DiagnosticsGeneral.adoc": { - Disco: Disco{ - Sections: map[string]DiscoSection{"NetworkInterface Type": {Skip: DiscoPurposeDataTypeRename}}, - }, - ZAP: ZAP{TemplatePath: "general-diagnostics-cluster"}, - }, - "src/service_device_management/DiagnosticsEthernet.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/ethernet_diagnostics.adoc", - }, - ZAP: ZAP{TemplatePath: "ethernet-network-diagnostics-cluster"}, - }, - "src/service_device_management/DiagnosticLogsCluster.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/logs_diagnostics.adoc", - }, - ZAP: ZAP{Domain: matter.DomainCHIP}, - }, - "src/service_device_management/DiagnosticsSoftware.adoc": { - ZAP: ZAP{TemplatePath: "software-diagnostics-cluster"}, - }, - "src/service_device_management/DiagnosticsThread.adoc": { - Disco: Disco{ - Sections: map[string]DiscoSection{ - "SecurityPolicy Type": { - Skip: DiscoPurposeDataTypeRename, - }, - "OperationalDatasetComponents Type": { - Skip: DiscoPurposeDataTypeRename, - }, - }, - }, - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/thread_nw_diagnostics.adoc", - }, - ZAP: ZAP{TemplatePath: "thread-network-diagnostics-cluster", - SuppressClusterDefinePrefix: true}, - }, - "src/service_device_management/DiagnosticsWiFi.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/wifi_diagnostics.adoc", - }, - ZAP: ZAP{TemplatePath: "wifi-network-diagnostics-cluster"}, - }, - "src/service_device_management/GeneralCommissioningCluster.adoc": { - Disco: Disco{ - Sections: map[string]DiscoSection{"BasicCommissioningInfo Type": {Skip: DiscoPurposeDataTypeRename}}, - }, - }, - "src/service_device_management/LocalizationConfiguration.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/Localization.adoc", - }, - }, - "src/service_device_management/LocalizationTimeFormat.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/timeformatlocalization.adoc", - }, - ZAP: ZAP{TemplatePath: "time-format-localization-cluster"}, - }, - "src/service_device_management/LocalizationUnit.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/unitlocalization.adoc", - }, - ZAP: ZAP{Domain: matter.DomainCHIP, - TemplatePath: "unit-localization-cluster"}, - }, - "src/service_device_management/OperationalCredentialCluster.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/node_operational_credentials.adoc", - }, - ZAP: ZAP{ - TemplatePath: "operational-credentials-cluster", - ClusterName: "Operational Credentials", - ClusterAliases: map[string][]string{ - "Operational Credentials": {"Node Operational Credentials"}, - }, - }, - }, - "src/service_device_management/OTAProvider.adoc": { - ZAP: ZAP{SkipFile: true}, - }, - "src/service_device_management/OTARequestor.adoc": { - ZAP: ZAP{SkipFile: true}, - }, - "src/service_device_management/PowerSourceConfigurationCluster.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/powersourceconfiguration.adoc", - }, - ZAP: ZAP{Domain: matter.DomainCHIP}, - }, - "src/service_device_management/PowerSourceCluster.adoc": { - TestPlan: TestPlan{ - TestPlanPath: "src/cluster/powersource.adoc", - }, - ZAP: ZAP{Domain: matter.DomainCHIP}, - }, - "src/service_device_management/TimeSync.adoc": { - ZAP: ZAP{TemplatePath: "time-synchronization-cluster"}, - }, -} +var Erratas = map[string]*Errata{} diff --git a/errata/testplan.go b/errata/testplan.go index 1403b84f..006301cb 100644 --- a/errata/testplan.go +++ b/errata/testplan.go @@ -1,5 +1,10 @@ package errata type TestPlan struct { - TestPlanPath string `yaml:"testplan-path,omitempty"` + TestPlanPath string `yaml:"testplan-path,omitempty"` + TestPlanPaths map[string]TestPlanPath `yaml:"testplan-paths,omitempty"` +} + +type TestPlanPath struct { + Path string `yaml:"path,omitempty"` } diff --git a/go.mod b/go.mod index 5b738e8c..52f83d5b 100644 --- a/go.mod +++ b/go.mod @@ -39,6 +39,7 @@ require ( github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/lestrrat-go/strftime v1.1.0 // indirect + github.com/mailgun/raymond/v2 v2.0.48 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect diff --git a/go.sum b/go.sum index afbe8e5f..ba17b8c3 100644 --- a/go.sum +++ b/go.sum @@ -72,6 +72,8 @@ github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffkt github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/lmittmann/tint v1.0.5 h1:NQclAutOfYsqs2F1Lenue6OoWCajs5wJcP3DfWVpePw= github.com/lmittmann/tint v1.0.5/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw= +github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -98,6 +100,7 @@ github.com/sethvargo/go-githubactions v1.3.0 h1:Kg633LIUV2IrJsqy2MfveiED/Ouo+H2P github.com/sethvargo/go-githubactions v1.3.0/go.mod h1:7/4WeHgYfSz9U5vwuToCK9KPnELVHAhGtRwLREOQV80= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= @@ -106,6 +109,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= @@ -126,6 +130,7 @@ golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -153,6 +158,7 @@ google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWn gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/src-d/go-errors.v1 v1.0.0 h1:cooGdZnCjYbeS1zb1s6pVAAimTdKceRrpn7aKOnNIfc= gopkg.in/src-d/go-errors.v1 v1.0.0/go.mod h1:q1cBlomlw2FnDBDNGlnh6X0jPihy+QxZfMMNxPCbdYg= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/find/map.go b/internal/find/map.go new file mode 100644 index 00000000..0596d188 --- /dev/null +++ b/internal/find/map.go @@ -0,0 +1,29 @@ +package find + +import "iter" + +func FirstPairFunc[K comparable, V any](m map[K]V, filter func(K) bool) (key K, value V, ok bool) { + for key, value = range FindPairFunc(m, filter) { + ok = true + return + } + return +} + +func FindPairFunc[K comparable, V any](m map[K]V, filter func(K) bool) iter.Seq2[K, V] { + return func(yield func(K, V) bool) { + for k, v := range m { + if filter(k) && yield(k, v) { + return + } + } + } +} + +func DeleteFunc[K comparable, V any](m map[K]V, filter func(K) bool) { + for k := range m { + if filter(k) { + delete(m, k) + } + } +} diff --git a/internal/handlebars/add.go b/internal/handlebars/add.go new file mode 100644 index 00000000..05f917cc --- /dev/null +++ b/internal/handlebars/add.go @@ -0,0 +1,11 @@ +package handlebars + +import ( + "strconv" + + "github.com/mailgun/raymond/v2" +) + +func AddHelper(augend int, addend int) raymond.SafeString { + return raymond.SafeString(strconv.Itoa(augend + addend)) +} diff --git a/internal/handlebars/brace.go b/internal/handlebars/brace.go new file mode 100644 index 00000000..3b224552 --- /dev/null +++ b/internal/handlebars/brace.go @@ -0,0 +1,7 @@ +package handlebars + +import "github.com/mailgun/raymond/v2" + +func BraceHelper(options *raymond.Options) raymond.SafeString { + return raymond.SafeString("{" + options.Fn() + "}") +} diff --git a/internal/handlebars/common.go b/internal/handlebars/common.go new file mode 100644 index 00000000..5eac242b --- /dev/null +++ b/internal/handlebars/common.go @@ -0,0 +1,13 @@ +package handlebars + +import "github.com/mailgun/raymond/v2" + +func RegisterCommonHelpers(t *raymond.Template) { + t.RegisterHelper("raw", RawHelper) + t.RegisterHelper("ifSet", IfSetHelper) + t.RegisterHelper("ifEqual", IfEqualHelper) + t.RegisterHelper("quote", QuoteHelper) + t.RegisterHelper("add", AddHelper) + t.RegisterHelper("brace", BraceHelper) + t.RegisterHelper("format", FormatHelper) +} diff --git a/internal/handlebars/format.go b/internal/handlebars/format.go new file mode 100644 index 00000000..01090712 --- /dev/null +++ b/internal/handlebars/format.go @@ -0,0 +1,11 @@ +package handlebars + +import ( + "fmt" + + "github.com/mailgun/raymond/v2" +) + +func FormatHelper(value any, format string) raymond.SafeString { + return raymond.SafeString(fmt.Sprintf(format, value)) +} diff --git a/internal/handlebars/ifset.go b/internal/handlebars/ifset.go new file mode 100644 index 00000000..73c8fef1 --- /dev/null +++ b/internal/handlebars/ifset.go @@ -0,0 +1,19 @@ +package handlebars + +import "github.com/mailgun/raymond/v2" + +func IfSetHelper(value any, options *raymond.Options) string { + switch value.(type) { + case nil: + return options.Inverse() + default: + return options.Fn() + } +} + +func IfEqualHelper(a, b any, options *raymond.Options) string { + if raymond.Str(a) == raymond.Str(b) { + return options.Fn() + } + return options.Inverse() +} diff --git a/internal/handlebars/iterate.go b/internal/handlebars/iterate.go new file mode 100644 index 00000000..f673b6a8 --- /dev/null +++ b/internal/handlebars/iterate.go @@ -0,0 +1,24 @@ +package handlebars + +import ( + "iter" + "reflect" +) + +func Iterate[T any](value any) iter.Seq[T] { + return func(yield func(T) bool) { + if reflect.TypeOf(value).Kind() != reflect.Slice { + return + } + slice := reflect.ValueOf(value) + for i := 0; i < slice.Len(); i++ { + el := slice.Index(i).Interface() + switch el := el.(type) { + case T: + if !yield(el) { + return + } + } + } + } +} diff --git a/internal/handlebars/overlay.go b/internal/handlebars/overlay.go new file mode 100644 index 00000000..2a3bc99d --- /dev/null +++ b/internal/handlebars/overlay.go @@ -0,0 +1,111 @@ +package handlebars + +import ( + "io/fs" + "os" + "path/filepath" + + "github.com/project-chip/alchemy/internal/files" +) + +type Overlay struct { + diskRoot string + embedded fs.FS + embeddedRoot string +} + +func NewOverlay(diskRoot string, embedded fs.FS, embeddedRoot string) *Overlay { + embedded, _ = fs.Sub(embedded, embeddedRoot) + return &Overlay{ + diskRoot: diskRoot, + embedded: embedded, + embeddedRoot: embeddedRoot, + } +} + +func (ov *Overlay) Open(path string) (fs.File, error) { + if ov.diskRoot != "" { + var err error + path, err = filepath.Rel(ov.embeddedRoot, path) + if err != nil { + return nil, err + } + path = filepath.Join(ov.diskRoot, path) + exists, err := files.Exists(path) + if err != nil { + return nil, err + } + if exists { + return os.Open(path) + } + } + return ov.embedded.Open(path) +} + +func (ov *Overlay) ReadDir(path string) ([]fs.DirEntry, error) { + if ov.diskRoot != "" { + return os.ReadDir(filepath.Join(ov.diskRoot, path)) + } + return fs.ReadDir(ov.embedded, path) +} + +func (ov *Overlay) ReadFile(path string) ([]byte, error) { + if ov.diskRoot != "" { + path = filepath.Join(ov.diskRoot, path) + exists, err := files.Exists(path) + if err != nil { + return nil, err + } + if exists { + return os.ReadFile(path) + } + } + return fs.ReadFile(ov.embedded, path) +} + +func (ov *Overlay) Flush() error { + if ov.embedded == nil { + return nil + } + if ov.diskRoot == "" { + return nil + } + exists, err := files.Exists(ov.diskRoot) + if err != nil { + return err + } + if !exists { + err = os.MkdirAll(ov.diskRoot, os.ModePerm) + if err != nil { + return err + } + } + return fs.WalkDir(ov.embedded, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + outPath := filepath.Join(ov.diskRoot, path) + if d.IsDir() { + err := os.MkdirAll(outPath, os.ModePerm) + if err != nil { + return err + } + } else { + exists, err := files.Exists(outPath) + if err != nil { + return err + } + if !exists { + b, err := fs.ReadFile(ov.embedded, path) + if err != nil { + return err + } + err = os.WriteFile(outPath, b, os.ModePerm) + if err != nil { + return err + } + } + } + return nil + }) +} diff --git a/internal/handlebars/quote.go b/internal/handlebars/quote.go new file mode 100644 index 00000000..4b8cff77 --- /dev/null +++ b/internal/handlebars/quote.go @@ -0,0 +1,11 @@ +package handlebars + +import ( + "strconv" + + "github.com/mailgun/raymond/v2" +) + +func QuoteHelper(s string) raymond.SafeString { + return raymond.SafeString(strconv.Quote(s)) +} diff --git a/internal/handlebars/raw.go b/internal/handlebars/raw.go new file mode 100644 index 00000000..f4f80ac8 --- /dev/null +++ b/internal/handlebars/raw.go @@ -0,0 +1,7 @@ +package handlebars + +import "github.com/mailgun/raymond/v2" + +func RawHelper(value string) raymond.SafeString { + return raymond.SafeString(value) +} diff --git a/internal/handlebars/template.go b/internal/handlebars/template.go new file mode 100644 index 00000000..36cd6e5a --- /dev/null +++ b/internal/handlebars/template.go @@ -0,0 +1,41 @@ +package handlebars + +import ( + "fmt" + "io/fs" + "path/filepath" + "strings" + + "github.com/mailgun/raymond/v2" +) + +func LoadTemplate(baseTemplate string, templateDir fs.FS) (*raymond.Template, error) { + + t := raymond.MustParse(baseTemplate) + err := fs.WalkDir(templateDir, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + ext := filepath.Ext(path) + if !strings.EqualFold(ext, ".handlebars") { + return nil + } + val, err := fs.ReadFile(templateDir, path) + if err != nil { + return fmt.Errorf("error reading template file %s: %w", path, err) + } + p, err := raymond.Parse(string(val)) + if err != nil { + return fmt.Errorf("error parsing template file %s: %w", path, err) + } + t.RegisterPartialTemplate(strings.TrimSuffix(path, filepath.Ext(path)), p) + return nil + }) + if err != nil { + return nil, err + } + return t, nil +} diff --git a/internal/pipeline/collective.go b/internal/pipeline/collective.go index 4c3c60bc..2f1904a7 100644 --- a/internal/pipeline/collective.go +++ b/internal/pipeline/collective.go @@ -11,8 +11,9 @@ type CollectiveProcessor[I, O any] interface { Process(cxt context.Context, inputs []*Data[I]) (outputs []*Data[O], err error) } -func processCollective[I, O any](cxt context.Context, processor CollectiveProcessor[I, O], input Map[string, *Data[I]]) (output Map[string, *Data[O]], err error) { - name := processor.Name() +type CollectiveProcess[I, O any] func(cxt context.Context, inputs []*Data[I]) (outputs []*Data[O], err error) + +func ProcessCollectiveFunc[I, O any](cxt context.Context, input Map[string, *Data[I]], name string, processor CollectiveProcess[I, O]) (output Map[string, *Data[O]], err error) { if len(name) > 0 { cyan.Fprintf(os.Stderr, "%s...\n", name) } @@ -25,7 +26,7 @@ func processCollective[I, O any](cxt context.Context, processor CollectiveProces return true }) var outputs []*Data[O] - outputs, err = processor.Process(cxt, inputs) + outputs, err = processor(cxt, inputs) if err != nil { return } diff --git a/internal/pipeline/individual.go b/internal/pipeline/individual.go new file mode 100644 index 00000000..5bda6339 --- /dev/null +++ b/internal/pipeline/individual.go @@ -0,0 +1,10 @@ +package pipeline + +import "context" + +type IndividualProcessor[I, O any] interface { + Processor + Process(cxt context.Context, input *Data[I], index int32, total int32) (outputs []*Data[O], extra []*Data[I], err error) +} + +type IndividualProcess[I, O any] func(cxt context.Context, input *Data[I], index int32, total int32) (outputs []*Data[O], extra []*Data[I], err error) diff --git a/internal/pipeline/once.go b/internal/pipeline/once.go new file mode 100644 index 00000000..a6f12104 --- /dev/null +++ b/internal/pipeline/once.go @@ -0,0 +1,16 @@ +package pipeline + +import "sync" + +type Once[T any] struct { + once sync.Once + err error + t T +} + +func (o *Once[T]) Do(f func() (T, error)) (T, error) { + o.once.Do(func() { + o.t, o.err = f() + }) + return o.t, o.err +} diff --git a/internal/pipeline/pipeline.go b/internal/pipeline/pipeline.go index 0cea1876..0341db10 100644 --- a/internal/pipeline/pipeline.go +++ b/internal/pipeline/pipeline.go @@ -31,7 +31,7 @@ func Process[I, O any](cxt context.Context, options Options, processor Processor err = fmt.Errorf("processor \"%s\" claimed to be collective, but does not implement CollectiveProcessor interface: %v", processor.Name(), proc) return } - return processCollective[I, O](cxt, proc, input) + return ProcessCollectiveFunc[I, O](cxt, input, proc.Name(), proc.Process) case ProcessorTypeIndividual: proc, ok := processor.(IndividualProcessor[I, O]) if !ok { @@ -77,8 +77,5 @@ func ProcessParallelFunc[I, O any](cxt context.Context, options Options, input M } }) - if err != nil { - return - } return processParallel[I, O](cxt, name, f, queue, total, !options.NoProgress) } diff --git a/internal/pipeline/processor.go b/internal/pipeline/processor.go index 347e34db..a3a2f037 100644 --- a/internal/pipeline/processor.go +++ b/internal/pipeline/processor.go @@ -1,7 +1,5 @@ package pipeline -import "context" - type ProcessorType int const ( @@ -13,10 +11,3 @@ type Processor interface { Name() string Type() ProcessorType } - -type IndividualProcessor[I, O any] interface { - Processor - Process(cxt context.Context, input *Data[I], index int32, total int32) (outputs []*Data[O], extra []*Data[I], err error) -} - -type IndividualProcess[I, O any] func(cxt context.Context, input *Data[I], index int32, total int32) (outputs []*Data[O], extra []*Data[I], err error) diff --git a/matter/bitmap.go b/matter/bitmap.go index 8d2d7af2..9fa4649a 100644 --- a/matter/bitmap.go +++ b/matter/bitmap.go @@ -15,7 +15,6 @@ import ( type Bitmap struct { entity - ParentEntity types.Entity `json:"-"` Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` diff --git a/matter/case.go b/matter/case.go index ecde8820..3ec4e773 100644 --- a/matter/case.go +++ b/matter/case.go @@ -17,17 +17,33 @@ func isSeparator(r rune) bool { return r == '.' || r == ' ' || r == '-' || r == '_' || r == '(' || r == ')' } +// Case turns a string with spaces into a valid Matter identifier in Pascal Case +// It is Unicode-aware, and preserves acronyms +// e.g. "Level control" becomes "LevelControl", "TV set" becomes "TVSet" func Case(s string) string { + return caseify(s, 0) +} + +// Case turns a string with spaces into a valid Matter identifier, with a custom separator rune +// e.g. "Level control" becomes "Level-Control", "TV set" becomes "TV-Set" +func CaseWithSeparator(s string, separator rune) string { + return caseify(s, separator) +} + +func caseify(s string, separator rune) string { runes := []rune(s) b := make([]byte, 0, len(runes)) var index int - nextUpper := true + upperCaseNextRune := true // We'll always start with a capital for index < len(runes) { r := runes[index] if unicode.IsUpper(r) { + if upperCaseNextRune && separator != 0 && len(b) > 0 { // If there's a supplied separator, append it, unless the string is empty + b = utf8.AppendRune(b, separator) + } var end int var endedBySeparator bool - for end = index + 1; end < len(runes); end++ { + for end = index + 1; end < len(runes); end++ { // Look for a run of upper-case letters if unicode.IsUpper(runes[end]) { if end == len(runes)-1 { // Last rune endedBySeparator = true @@ -41,13 +57,17 @@ func Case(s string) string { break } if end-index > 1 { + _, isAcronym := acronyms.Load(string(runes[index:end])) if isAcronym || endedBySeparator { + // If this run of upper-case letters is a known acronym, or + // it ends the string, preserve it for index < end { b = utf8.AppendRune(b, runes[index]) index++ } } else { + // Otherwise, lower-case the remainder of the block of upper-case letters b = utf8.AppendRune(b, runes[index]) index++ for index < end-1 { @@ -58,28 +78,31 @@ func Case(s string) string { } index = end } else { + // It's just the one upper-case letter b = utf8.AppendRune(b, runes[index]) index++ } - nextUpper = false + upperCaseNextRune = false continue } else if unicode.IsLower(r) { - if nextUpper { + if upperCaseNextRune { + if separator != 0 { + b = utf8.AppendRune(b, separator) + } b = utf8.AppendRune(b, unicode.ToUpper(r)) - nextUpper = false + upperCaseNextRune = false } else { b = utf8.AppendRune(b, r) } } else if unicode.IsNumber(r) { b = utf8.AppendRune(b, r) - nextUpper = true + upperCaseNextRune = true } else { - nextUpper = isSeparator(r) + upperCaseNextRune = isSeparator(r) } index++ } return string(b) - } type charClass uint8 diff --git a/matter/case_test.go b/matter/case_test.go index afd8bbf6..10712a39 100644 --- a/matter/case_test.go +++ b/matter/case_test.go @@ -7,6 +7,11 @@ type caseTest struct { output string } +type caseSeperatorTest struct { + caseTest + separator rune +} + var caseTests = []caseTest{ {"TCP Wait", "TCPWait"}, {"VendorID", "VendorID"}, @@ -18,6 +23,18 @@ var caseTests = []caseTest{ {"Set Tv", "SetTv"}, } +var caseSeparatorTests = []caseSeperatorTest{ + {caseTest: caseTest{"TCP Wait", "TCP_Wait"}, separator: '_'}, + {caseTest: caseTest{"VendorID", "VendorID"}, separator: '_'}, + {caseTest: caseTest{"VendorID", "VendorID"}, separator: '_'}, + {caseTest: caseTest{"Vendor Attribute", "Vendor_Attribute"}, separator: '_'}, + {caseTest: caseTest{"HTTP Request", "HTTP_Request"}, separator: '_'}, + {caseTest: caseTest{"TV Set", "TV_Set"}, separator: '_'}, + {caseTest: caseTest{"Set TV", "Set_TV"}, separator: '_'}, + {caseTest: caseTest{"Set Tv", "Set_Tv"}, separator: '_'}, + {caseTest: caseTest{"LoggedOut", "LoggedOut"}, separator: '_'}, +} + func TestSuite(t *testing.T) { AddCaseAcronym("ID") for _, ct := range caseTests { @@ -26,5 +43,10 @@ func TestSuite(t *testing.T) { t.Errorf("casing failed; input %s; expected %s, got %s", ct.input, ct.output, out) } } - + for _, ct := range caseSeparatorTests { + out := CaseWithSeparator(ct.input, ct.separator) + if out != ct.output { + t.Errorf("casing with separator failed; input %s; expected %s, got %s", ct.input, ct.output, out) + } + } } diff --git a/matter/cluster.go b/matter/cluster.go index 333375e5..2bc67249 100644 --- a/matter/cluster.go +++ b/matter/cluster.go @@ -30,6 +30,9 @@ func NewClusterGroup(name string, source asciidoc.Element, clusters []*Cluster) entity: entity{source: source}, Clusters: clusters, } + for _, cluster := range clusters { + cluster.parentEntity = cg + } cg.AssociatedDataTypes.parentEntity = cg return cg } @@ -44,12 +47,12 @@ func (c ClusterGroup) Explode() []*Cluster { type Cluster struct { entity - ID *Number `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - Revisions []*Revision `json:"revisions,omitempty"` - Parent *Cluster `json:"-"` - Conformance conformance.Set `json:"conformance,omitempty"` + ID *Number `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Revisions []*Revision `json:"revisions,omitempty"` + ParentCluster *Cluster `json:"-"` + Conformance conformance.Set `json:"conformance,omitempty"` ClusterClassification @@ -73,7 +76,7 @@ func (c *Cluster) EntityType() types.EntityType { } func (c *Cluster) Inherit(parent *Cluster) (linkedEntities []types.Entity, err error) { - c.Parent = parent + c.ParentCluster = parent if parent.Features != nil { if c.Features == nil || len(c.Features.Bits) == 0 { c.Features = parent.Features.Clone() diff --git a/matter/conformance/grammar/text.peg b/matter/conformance/grammar/text.peg index 5ec9202d..b3acfebf 100644 --- a/matter/conformance/grammar/text.peg +++ b/matter/conformance/grammar/text.peg @@ -21,7 +21,7 @@ Lowercase <- [a-z] { return string(c.text), nil } -SameLineString <- [^\n]+ { +SameLineString <- [^\r\n]+ { //debug("matched same line string %s\n", string(c.text)) return string(c.text), nil } diff --git a/matter/conformance/parse.go b/matter/conformance/parse.go index c82d9f14..763ad9ab 100644 --- a/matter/conformance/parse.go +++ b/matter/conformance/parse.go @@ -128,3 +128,18 @@ func IsBlank(conformance Conformance) bool { } return false } + +func IsGeneric(conformance Conformance) bool { + switch conformance := conformance.(type) { + case *Generic: + return conformance.raw != "" + case Set: + if len(conformance) == 1 { + mc, ok := conformance[0].(*Generic) + if ok { + return mc.raw != "" + } + } + } + return false +} diff --git a/matter/conformance/parser.go b/matter/conformance/parser.go index 75ed79f8..80b04f28 100644 --- a/matter/conformance/parser.go +++ b/matter/conformance/parser.go @@ -1759,8 +1759,8 @@ var g = &grammar{ pos: position{line: 398, col: 19, offset: 8742}, expr: &charClassMatcher{ pos: position{line: 398, col: 19, offset: 8742}, - val: "[^\\n]", - chars: []rune{'\n'}, + val: "[^\\r\\n]", + chars: []rune{'\r', '\n'}, ignoreCase: false, inverted: true, }, @@ -1770,11 +1770,11 @@ var g = &grammar{ { name: "_", displayName: "\"whitespace\"", - pos: position{line: 402, col: 1, offset: 8789}, + pos: position{line: 402, col: 1, offset: 8791}, expr: &zeroOrMoreExpr{ - pos: position{line: 402, col: 19, offset: 8807}, + pos: position{line: 402, col: 19, offset: 8809}, expr: &charClassMatcher{ - pos: position{line: 402, col: 19, offset: 8807}, + pos: position{line: 402, col: 19, offset: 8809}, val: "[ \\t\\r\\n]", chars: []rune{' ', '\t', '\r', '\n'}, ignoreCase: false, @@ -1784,9 +1784,9 @@ var g = &grammar{ }, { name: "Comma", - pos: position{line: 404, col: 1, offset: 8819}, + pos: position{line: 404, col: 1, offset: 8821}, expr: &litMatcher{ - pos: position{line: 404, col: 10, offset: 8828}, + pos: position{line: 404, col: 10, offset: 8830}, val: ",", ignoreCase: false, want: "\",\"", @@ -1794,11 +1794,11 @@ var g = &grammar{ }, { name: "EOF", - pos: position{line: 406, col: 1, offset: 8834}, + pos: position{line: 406, col: 1, offset: 8836}, expr: ¬Expr{ - pos: position{line: 406, col: 8, offset: 8841}, + pos: position{line: 406, col: 8, offset: 8843}, expr: &anyMatcher{ - line: 406, col: 9, offset: 8842, + line: 406, col: 9, offset: 8844, }, }, }, diff --git a/matter/constraint/empty.go b/matter/constraint/empty.go index 58775a09..40987bd7 100644 --- a/matter/constraint/empty.go +++ b/matter/constraint/empty.go @@ -23,7 +23,7 @@ func (c *EmptyLimit) Equal(o Limit) bool { } func (c *EmptyLimit) Min(cc Context) (min types.DataTypeExtreme) { - return types.DataTypeExtreme{Type: types.DataTypeExtremeTypeEmpty, Format: types.NumberFormatHex} + return types.DataTypeExtreme{Type: types.DataTypeExtremeTypeEmptyList, Format: types.NumberFormatHex} } func (c *EmptyLimit) Max(cc Context) (max types.DataTypeExtreme) { diff --git a/matter/datatypes.go b/matter/datatypes.go index 30a2cbf5..ecbad403 100644 --- a/matter/datatypes.go +++ b/matter/datatypes.go @@ -68,52 +68,59 @@ type AssociatedDataTypes struct { func (adt *AssociatedDataTypes) AddBitmaps(bitmaps ...*Bitmap) { for _, bm := range bitmaps { - if bm.ParentEntity != nil { - if _, ok := bm.ParentEntity.(*ClusterGroup); !ok { + parentEntity := bm.Parent() + if parentEntity != nil { + if _, ok := parentEntity.(*ClusterGroup); !ok { slog.Warn("Bitmap belongs to multiple parents", slog.String("name", bm.Name), log.Path("source", bm), LogEntity(adt.parentEntity)) } continue } - bm.ParentEntity = adt.parentEntity + bm.parent = adt.parentEntity } adt.Bitmaps = append(adt.Bitmaps, bitmaps...) } func (adt *AssociatedDataTypes) AddEnums(enums ...*Enum) { for _, e := range enums { - if e.ParentEntity != nil { - if _, ok := e.ParentEntity.(*ClusterGroup); !ok { + if e.parent != nil { + if _, ok := e.parent.(*ClusterGroup); !ok { slog.Warn("Enum belongs to multiple parents", slog.String("name", e.Name), log.Path("source", e), LogEntity(adt.parentEntity)) } continue } - e.ParentEntity = adt.parentEntity + e.parent = adt.parentEntity } adt.Enums = append(adt.Enums, enums...) } func (adt *AssociatedDataTypes) AddStructs(structs ...*Struct) { for _, s := range structs { - if s.ParentEntity != nil { - if _, ok := s.ParentEntity.(*ClusterGroup); !ok { + if s.parent != nil { + if _, ok := s.parent.(*ClusterGroup); !ok { slog.Warn("Struct belongs to multiple parents", slog.String("name", s.Name), log.Path("source", s), LogEntity(adt.parentEntity)) } continue } - s.ParentEntity = adt.parentEntity + s.parent = adt.parentEntity } adt.Structs = append(adt.Structs, structs...) } func (adt *AssociatedDataTypes) AddTypeDefs(typeDefs ...*TypeDef) { for _, td := range typeDefs { - if td.ParentEntity != nil { - if _, ok := td.ParentEntity.(*ClusterGroup); !ok { + if td.parent != nil { + if _, ok := td.parent.(*ClusterGroup); !ok { slog.Warn("TypeDef belongs to multiple parents", slog.String("name", td.Name), log.Path("source", td), LogEntity(adt.parentEntity)) } continue } - td.ParentEntity = adt.parentEntity + td.parent = adt.parentEntity } adt.TypeDefs = append(adt.TypeDefs, typeDefs...) } + +// This really exists to allow patching ZAP +func (adt *AssociatedDataTypes) MoveStruct(s *Struct) { + adt.Structs = append(adt.Structs, s) + s.parent = adt.parentEntity +} diff --git a/matter/devicetype.go b/matter/devicetype.go index 9b3e1103..fc763d65 100644 --- a/matter/devicetype.go +++ b/matter/devicetype.go @@ -18,7 +18,8 @@ type DeviceType struct { Class string `json:"class,omitempty"` Scope string `json:"scope,omitempty"` - Conditions []*Condition `json:"conditions,omitempty"` + Conditions []*Condition `json:"conditions,omitempty"` + DeviceTypeRequirements []*DeviceTypeRequirement `json:"deviceTypeRequirements,omitempty"` ClusterRequirements []*ClusterRequirement `json:"clusterRequirements,omitempty"` ElementRequirements []*ElementRequirement `json:"elementRequirements,omitempty"` @@ -67,6 +68,13 @@ type ElementRequirement struct { Cluster *Cluster `json:"cluster,omitempty"` } +type DeviceTypeRequirement struct { + DeviceTypeID *Number `json:"deviceTypeId,omitempty"` + DeviceTypeName string `json:"deviceTypeName,omitempty"` + Constraint constraint.Constraint `json:"constraint,omitempty"` + Conformance conformance.Set `json:"conformance,omitempty"` +} + type ComposedDeviceTypeRequirement struct { DeviceTypeID *Number `json:"deviceTypeId,omitempty"` DeviceTypeName string `json:"deviceTypeName,omitempty"` diff --git a/matter/entity.go b/matter/entity.go index 16471951..c8e232ea 100644 --- a/matter/entity.go +++ b/matter/entity.go @@ -2,12 +2,18 @@ package matter import ( "github.com/project-chip/alchemy/asciidoc" + "github.com/project-chip/alchemy/matter/types" ) type entity struct { + parent types.Entity source asciidoc.Element } +func (e entity) Parent() types.Entity { + return e.parent +} + func (e entity) Source() asciidoc.Element { return e.source } diff --git a/matter/enum.go b/matter/enum.go index 1555c910..69649dce 100644 --- a/matter/enum.go +++ b/matter/enum.go @@ -10,7 +10,6 @@ import ( type Enum struct { entity - ParentEntity types.Entity `json:"-"` Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` diff --git a/matter/feature.go b/matter/feature.go index 322a28f4..84d076e3 100644 --- a/matter/feature.go +++ b/matter/feature.go @@ -2,6 +2,7 @@ package matter import ( "encoding/json" + "iter" "github.com/project-chip/alchemy/matter/conformance" "github.com/project-chip/alchemy/matter/types" @@ -31,6 +32,17 @@ func (fs *Features) Identifier(id string) (types.Entity, bool) { return fs.Bitmap.Identifier(id) } +func (fs *Features) FeatureBits() iter.Seq[*Feature] { + return func(yield func(*Feature) bool) { + for _, b := range fs.Bits { + f, ok := b.(*Feature) + if ok && !yield(f) { + return + } + } + } +} + type Feature struct { BitmapBit Code string diff --git a/matter/field.go b/matter/field.go index 525b51f2..8e9c94fa 100644 --- a/matter/field.go +++ b/matter/field.go @@ -25,12 +25,12 @@ type Field struct { entityType types.EntityType } -func NewField(source asciidoc.Element) *Field { - return &Field{entity: entity{source: source}, entityType: types.EntityTypeStructField} +func NewField(source asciidoc.Element, parent types.Entity) *Field { + return &Field{entity: entity{source: source, parent: parent}, entityType: types.EntityTypeStructField} } -func NewAttribute(source asciidoc.Element) *Field { - return &Field{entity: entity{source: source}, entityType: types.EntityTypeAttribute} +func NewAttribute(source asciidoc.Element, parent types.Entity) *Field { + return &Field{entity: entity{source: source, parent: parent}, entityType: types.EntityTypeAttribute} } func (f *Field) GetConformance() conformance.Set { diff --git a/matter/sections.go b/matter/sections.go index 60cbe713..d889523b 100644 --- a/matter/sections.go +++ b/matter/sections.go @@ -28,6 +28,7 @@ const ( SectionEvent SectionNamespace SectionConditions + SectionDeviceTypeRequirements SectionClusterRequirements SectionClusterRestrictions SectionElementRequirements @@ -61,6 +62,7 @@ var TopLevelSectionOrders = map[DocType][]Section{ SectionRevisionHistory, SectionClassification, SectionConditions, + SectionDeviceTypeRequirements, SectionClusterRequirements, SectionClusterRestrictions, SectionElementRequirements, @@ -97,6 +99,7 @@ var sectionTypeStrings = map[Section]string{ SectionEvent: "Event", SectionNamespace: "Namespace", SectionConditions: "Conditions", + SectionDeviceTypeRequirements: "DeviceTypeRequirements", SectionClusterRequirements: "ClusterRequirements", SectionClusterRestrictions: "ClusterRestrictions", SectionElementRequirements: "ElementRequirements", @@ -142,6 +145,7 @@ var sectionTypeNames = map[Section]string{ SectionEvents: "Events", SectionNamespace: "Namespace", SectionConditions: "Conditions", + SectionDeviceTypeRequirements: "Device Type Requirements", SectionClusterRequirements: "Cluster Requirements", SectionClusterRestrictions: "Cluster Restrictions", SectionElementRequirements: "Element Requirements", diff --git a/matter/spec/attributes.go b/matter/spec/attributes.go index 7efea3a2..40547c71 100644 --- a/matter/spec/attributes.go +++ b/matter/spec/attributes.go @@ -21,7 +21,7 @@ func (s *Section) toAttributes(d *Doc, cluster *matter.Cluster, entityMap map[as } attributeMap := make(map[string]*matter.Field) for row := range ti.Body() { - attr := matter.NewAttribute(row) + attr := matter.NewAttribute(row, cluster) attr.ID, err = ti.ReadID(row, matter.TableColumnID) if err != nil { return diff --git a/matter/spec/bitmap.go b/matter/spec/bitmap.go index 88e3f53e..a51d488b 100644 --- a/matter/spec/bitmap.go +++ b/matter/spec/bitmap.go @@ -18,9 +18,8 @@ func (s *Section) toBitmap(d *Doc, entityMap map[asciidoc.Attributable][]types.E dt := s.GetDataType() if dt == nil { dt = types.NewDataType(types.BaseDataTypeMap8, false) - } - - if !dt.IsMap() { + slog.Warn("Bitmap does not declare its derived data type; assuming map8", log.Element("path", d.Path, s.Base), slog.String("bitmap", name)) + } else if !dt.IsMap() { return nil, fmt.Errorf("unknown bitmap data type: %s", dt.Name) } diff --git a/matter/spec/build.go b/matter/spec/build.go index 91cc85eb..572234b6 100644 --- a/matter/spec/build.go +++ b/matter/spec/build.go @@ -93,6 +93,14 @@ func (sp *Builder) buildSpec(docs []*Doc) (spec *Specification, err error) { addClusterToSpec(spec, d, m) case *matter.DeviceType: spec.DeviceTypes = append(spec.DeviceTypes, m) + if m.ID.Valid() { + if existing, ok := spec.DeviceTypesByID[m.ID.Value()]; ok { + slog.Error("duplicate device type ID", slog.String("deviceTypeId", m.ID.HexString()), log.Path("previousSource", existing), log.Path("newSource", m)) + } else { + spec.DeviceTypesByID[m.ID.Value()] = m + } + + } case *matter.Namespace: spec.Namespaces = append(spec.Namespaces, m) default: @@ -214,6 +222,16 @@ func addClusterToSpec(spec *Specification, d *Doc, m *matter.Cluster) { slog.Warn("Duplicate cluster ID", slog.String("clusterId", m.ID.HexString()), slog.String("clusterName", m.Name), slog.String("existingClusterName", existing.Name)) } spec.ClustersByID[m.ID.Value()] = m + } else { + idText := m.ID.Text() + if !strings.EqualFold(idText, "n/a") { + if strings.EqualFold(idText, "ID-TBD") { + slog.Warn("Cluster has not yet been assigned an ID; this may cause issues with generated code", slog.String("clusterName", m.Name)) + } else { + slog.Warn("Cluster has invalid ID", slog.String("clusterId", idText), slog.String("clusterName", m.Name)) + + } + } } existing, ok := spec.ClustersByName[m.Name] if ok { diff --git a/matter/spec/commands.go b/matter/spec/commands.go index 0fe4b82c..0b8f0c8f 100644 --- a/matter/spec/commands.go +++ b/matter/spec/commands.go @@ -67,7 +67,7 @@ func (cf *commandFactory) Details(d *Doc, s *Section, entityMap map[asciidoc.Att } return } - c.Fields, err = d.readFields(ti, types.EntityTypeCommandField) + c.Fields, err = d.readFields(ti, types.EntityTypeCommandField, c) if err != nil { return } diff --git a/matter/spec/datatypes.go b/matter/spec/datatypes.go index 07ae6f86..e5cda2c0 100644 --- a/matter/spec/datatypes.go +++ b/matter/spec/datatypes.go @@ -182,9 +182,9 @@ func disambiguateDataType(entities map[types.Entity]*matter.Cluster, cluster *ma } // OK, if the data type is defined on the direct parent of this cluster, take that one - if cluster.Parent != nil { + if cluster.ParentCluster != nil { for m, c := range entities { - if c != nil && c == cluster.Parent { + if c != nil && c == cluster.ParentCluster { return m } } diff --git a/matter/spec/devicetype.go b/matter/spec/devicetype.go index 55ac38b6..709ebe19 100644 --- a/matter/spec/devicetype.go +++ b/matter/spec/devicetype.go @@ -49,6 +49,8 @@ func (s *Section) toDeviceTypes(d *Doc) (entities []types.Entity, err error) { c.ComposedDeviceTypeRequirements, err = s.toComposedDeviceTypeRequirements(d) case matter.SectionConditions: c.Conditions, err = s.toConditions(d) + case matter.SectionDeviceTypeRequirements: + c.DeviceTypeRequirements, err = s.toDeviceTypeRequirements(d) case matter.SectionRevisionHistory: c.Revisions, err = readRevisionHistory(d, s) default: diff --git a/matter/spec/enum.go b/matter/spec/enum.go index d3570348..8d08c88d 100644 --- a/matter/spec/enum.go +++ b/matter/spec/enum.go @@ -22,9 +22,8 @@ func (s *Section) toEnum(d *Doc, entityMap map[asciidoc.Attributable][]types.Ent dt := s.GetDataType() if dt == nil { dt = types.NewDataType(types.BaseDataTypeEnum8, false) - } - - if !dt.IsEnum() { + slog.Warn("Enum does not declare its derived data type; assuming enum8", log.Element("path", d.Path, s.Base), slog.String("enum", name)) + } else if !dt.IsEnum() { return nil, fmt.Errorf("unknown enum data type: %s", dt.Name) } diff --git a/matter/spec/event.go b/matter/spec/event.go index 5e389728..a43b2259 100644 --- a/matter/spec/event.go +++ b/matter/spec/event.go @@ -73,7 +73,7 @@ func (cf *eventFactory) Details(d *Doc, s *Section, entityMap map[asciidoc.Attri err = fmt.Errorf("failed reading %s event fields: %w", s.Name, err) return } - e.Fields, err = d.readFields(ti, types.EntityTypeEventField) + e.Fields, err = d.readFields(ti, types.EntityTypeEventField, e) if err != nil { return } diff --git a/matter/spec/field.go b/matter/spec/field.go index c3a60add..602ae2f9 100644 --- a/matter/spec/field.go +++ b/matter/spec/field.go @@ -15,10 +15,10 @@ import ( "github.com/project-chip/alchemy/matter/types" ) -func (d *Doc) readFields(ti *TableInfo, entityType types.EntityType) (fields []*matter.Field, err error) { +func (d *Doc) readFields(ti *TableInfo, entityType types.EntityType, parent types.Entity) (fields []*matter.Field, err error) { ids := make(map[uint64]*matter.Field) for row := range ti.Body() { - f := matter.NewField(row) + f := matter.NewField(row, parent) f.Name, err = ti.ReadValue(row, matter.TableColumnName) if err != nil { return diff --git a/matter/spec/requirements.go b/matter/spec/requirements.go index bf52b89f..bd1a0396 100644 --- a/matter/spec/requirements.go +++ b/matter/spec/requirements.go @@ -87,6 +87,34 @@ func (s *Section) toElementRequirements(d *Doc) (elementRequirements []*matter.E return } +func (s *Section) toDeviceTypeRequirements(d *Doc) (deviceTypeRequirements []*matter.DeviceTypeRequirement, err error) { + var ti *TableInfo + ti, err = parseFirstTable(d, s) + if err != nil { + if err == ErrNoTableFound { + err = nil + } else { + err = fmt.Errorf("error reading element requirements table: %w", err) + } + return + } + for row := range ti.Body() { + var cr matter.DeviceTypeRequirement + cr.DeviceTypeID, err = ti.ReadID(row, matter.TableColumnDeviceID) + if err != nil { + return + } + cr.DeviceTypeName, err = ti.ReadString(row, matter.TableColumnName) + if err != nil { + return + } + cr.Constraint = ti.ReadConstraint(row, matter.TableColumnConstraint) + cr.Conformance = ti.ReadConformance(row, matter.TableColumnConformance) + deviceTypeRequirements = append(deviceTypeRequirements, &cr) + } + return +} + func (s *Section) toComposedDeviceTypeRequirements(d *Doc) (composedRequirements []*matter.ComposedDeviceTypeRequirement, err error) { var ti *TableInfo ti, err = parseFirstTable(d, s) diff --git a/matter/spec/section.go b/matter/spec/section.go index 1805e603..a27652af 100644 --- a/matter/spec/section.go +++ b/matter/spec/section.go @@ -224,6 +224,8 @@ func getSectionType(parent *Section, section *Section) matter.Section { return matter.SectionDerivedClusterNamespace case "global elements": return matter.SectionGlobalElements + case "device type requirements": + return matter.SectionDeviceTypeRequirements } switch parent.SecType { case matter.SectionTop, matter.SectionCluster, matter.SectionDeviceType: diff --git a/matter/spec/spec.go b/matter/spec/spec.go index c9982647..c91304d5 100644 --- a/matter/spec/spec.go +++ b/matter/spec/spec.go @@ -16,8 +16,10 @@ type Specification struct { Clusters map[*matter.Cluster]struct{} ClustersByID map[uint64]*matter.Cluster ClustersByName map[string]*matter.Cluster - DeviceTypes []*matter.DeviceType - BaseDeviceType *matter.DeviceType + + DeviceTypes []*matter.DeviceType + DeviceTypesByID map[uint64]*matter.DeviceType + BaseDeviceType *matter.DeviceType Namespaces []*matter.Namespace @@ -40,11 +42,12 @@ type Specification struct { func newSpec() *Specification { return &Specification{ - Clusters: make(map[*matter.Cluster]struct{}), - ClustersByID: make(map[uint64]*matter.Cluster), - ClustersByName: make(map[string]*matter.Cluster), - ClusterRefs: ClusterRefs{refs: make(map[types.Entity]map[*matter.Cluster]struct{})}, - DocRefs: make(map[types.Entity]*Doc), + Clusters: make(map[*matter.Cluster]struct{}), + ClustersByID: make(map[uint64]*matter.Cluster), + ClustersByName: make(map[string]*matter.Cluster), + ClusterRefs: ClusterRefs{refs: make(map[types.Entity]map[*matter.Cluster]struct{})}, + DeviceTypesByID: make(map[uint64]*matter.DeviceType), + DocRefs: make(map[types.Entity]*Doc), bitmapIndex: make(map[string]*matter.Bitmap), enumIndex: make(map[string]*matter.Enum), diff --git a/matter/spec/struct.go b/matter/spec/struct.go index d041c175..8e7fabad 100644 --- a/matter/spec/struct.go +++ b/matter/spec/struct.go @@ -33,7 +33,7 @@ func (s *Section) toStruct(d *Doc, entityMap map[asciidoc.Attributable][]types.E } } } - ms.Fields, err = d.readFields(ti, types.EntityTypeStructField) + ms.Fields, err = d.readFields(ti, types.EntityTypeStructField, ms) if err != nil { return } diff --git a/matter/spec/table_info.go b/matter/spec/table_info.go index 49bec80c..241e35f9 100644 --- a/matter/spec/table_info.go +++ b/matter/spec/table_info.go @@ -209,7 +209,12 @@ func (ti *TableInfo) ReadConformance(row *asciidoc.TableRow, column matter.Table return conformance.Set{&conformance.Mandatory{}} } s = newLineReplacer.Replace(s) - return conformance.ParseConformance(matter.StripTypeSuffixes(s)) + s = matter.StripTypeSuffixes(s) + conf := conformance.ParseConformance(s) + if conformance.IsGeneric(conf) { + slog.Error("failed parsing conformance cell", slog.String("value", s)) + } + return conf } func (ti *TableInfo) buildRowConformance(cellElements asciidoc.Set, sb *strings.Builder) (source asciidoc.Element) { diff --git a/matter/spec/type.go b/matter/spec/type.go index b1049b0c..f71f3f67 100644 --- a/matter/spec/type.go +++ b/matter/spec/type.go @@ -92,14 +92,15 @@ func (doc *Doc) determineDocType() (matter.DocType, error) { return matter.DocTypeSoftAP, nil } } - slog.Debug("could not determine doc type", "path", doc.Path) - return matter.DocTypeUnknown, nil + guess := doc.guessDocType() + slog.Debug("could not determine doc type", "path", doc.Path, slog.String("guessing", guess.String())) + return guess, nil } func (doc *Doc) guessDocType() matter.DocType { firstSection := parse.FindFirst[*Section](doc.Elements()) if firstSection != nil { - if strings.HasSuffix(strings.ToLower(firstSection.Name), " cluster") { + if text.HasCaseInsensitiveSuffix(firstSection.Name, " cluster") { return matter.DocTypeCluster } } diff --git a/matter/struct.go b/matter/struct.go index c3b031e0..3c4abfc2 100644 --- a/matter/struct.go +++ b/matter/struct.go @@ -7,7 +7,6 @@ import ( type Struct struct { entity - ParentEntity types.Entity `json:"-"` Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` diff --git a/matter/typedef.go b/matter/typedef.go index 693d55f5..a04b7ade 100644 --- a/matter/typedef.go +++ b/matter/typedef.go @@ -7,7 +7,6 @@ import ( type TypeDef struct { entity - ParentEntity types.Entity `json:"-"` Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` diff --git a/matter/types/entity.go b/matter/types/entity.go index faf616dc..493a6428 100644 --- a/matter/types/entity.go +++ b/matter/types/entity.go @@ -34,6 +34,7 @@ const ( type Entity interface { EntityType() EntityType Source() asciidoc.Element + Parent() Entity } func (et EntityType) String() string { diff --git a/matter/types/extreme.go b/matter/types/extreme.go index b6dd43bc..8fc96e2b 100644 --- a/matter/types/extreme.go +++ b/matter/types/extreme.go @@ -13,7 +13,7 @@ const ( DataTypeExtremeTypeInt64 DataTypeExtremeTypeUInt64 DataTypeExtremeTypeNull - DataTypeExtremeTypeEmpty + DataTypeExtremeTypeEmptyList ) type DataTypeExtreme struct { @@ -87,7 +87,7 @@ func (ce *DataTypeExtreme) ZapString(dataType *DataType) string { } case DataTypeExtremeTypeUInt64: return ce.formatUint64(dataType, ce.UInt64) - case DataTypeExtremeTypeNull, DataTypeExtremeTypeEmpty: + case DataTypeExtremeTypeNull, DataTypeExtremeTypeEmptyList: return "" } return "" @@ -139,7 +139,7 @@ func (ce *DataTypeExtreme) DataModelString(dataType *DataType) string { return ce.formatUint64(dataType, ce.UInt64) case DataTypeExtremeTypeNull: return "null" - case DataTypeExtremeTypeEmpty: + case DataTypeExtremeTypeEmptyList: return "empty" } return "" diff --git a/testing/generate/command.go b/testing/generate/command.go new file mode 100644 index 00000000..fd5d6c6f --- /dev/null +++ b/testing/generate/command.go @@ -0,0 +1,256 @@ +package generate + +import ( + "fmt" + "log/slog" + "strings" + + "github.com/goccy/go-yaml" + "github.com/iancoleman/strcase" + "github.com/mailgun/raymond/v2" + "github.com/project-chip/alchemy/internal/log" + "github.com/project-chip/alchemy/matter" + "github.com/project-chip/alchemy/matter/spec" + "github.com/project-chip/alchemy/matter/types" + "github.com/project-chip/alchemy/testing/parse" +) + +func getArg(arg any) (name string, value any, ok bool) { + var n any + switch v := arg.(type) { + case map[string]any: + + n, ok = v["name"] + if !ok { + return + } + name, ok = n.(string) + if !ok { + return + } + case yaml.MapSlice: + n, ok = parse.ValueFromMapSlice(v, "name") + if !ok { + return + } + name, ok = n.(string) + if !ok { + return + } + value, ok = parse.ValueFromMapSlice(v, "value") + + } + return +} + +func getCluster(spec *spec.Specification, t *test, ts *testStep) (clusterName string, cluster *matter.Cluster) { + clusterName = ts.Cluster + if clusterName == "" { + clusterName = t.Config.Cluster + } + var ok bool + cluster, ok = spec.ClustersByName[clusterName] + if !ok { + slog.Warn("Unknown cluster in test", slog.String("path", ts.Parent.Path), slog.String("clusterName", clusterName)) + } + return +} + +func commandArgValue(name string, cluster *matter.Cluster, field *matter.Field, value any) string { + if field == nil { + return fmt.Sprintf("unknown field: %s", name) + } + if field.Type == nil { + return fmt.Sprintf("%v=%s", strcase.ToLowerCamel(name), string(pythonValueHelper(value))) + } + if field.Type.Entity == nil { + return fmt.Sprintf("%v=%s", strcase.ToLowerCamel(name), string(pythonValueHelper(value))) + + } + var parentEntity types.Entity + var namespace string + var objectGroup string + var valName string + var objectName string + switch entity := field.Type.Entity.(type) { + case *matter.Enum: + parentEntity = entity.Parent() + objectGroup = "Enums" + objectName = entity.Name + switch value := value.(type) { + case uint64: + for _, val := range entity.Values { + if val.Value.Valid() && val.Value.Value() == value { + valName = "k" + val.Name + break + } + } + default: + slog.Info("unknown arg type", slog.String("name", name), log.Type("type", value)) + } + case *matter.Bitmap: + parentEntity = entity.Parent() + objectGroup = "Bitmaps" + objectName = entity.Name + switch value := value.(type) { + case uint64: + for _, val := range entity.Bits { + from, to, err := val.Bits() + if err != nil || from != to { + continue + } + if from == value { + valName = "k" + val.Name() + break + } + } + + default: + slog.Info("unknown arg type", slog.String("name", name), log.Type("type", value)) + } + case *matter.Struct: + //Clusters.Objects.ApplicationLauncher.Structs.ApplicationStruct.({CatalogVendorID: catalogVendorId, ApplicationID: NonAvailableApp})) + /// preset = cluster.Structs.PresetStruct(presetHandle=presetHandle, presetScenario=presetScenario, builtIn=builtIn) + parentEntity = entity.Parent() + objectGroup = "Structs" + objectName = entity.Name + slog.Warn("struct argument entity", slog.String("name", name), log.Type("type", value)) + + default: + slog.Warn("unsupported argument entity", log.Type("type", entity)) + } + if parentEntity != nil { + switch parentEntity := parentEntity.(type) { + case *matter.Cluster: + if parentEntity == cluster { + namespace = "cluster" + } else { + namespace = fmt.Sprintf("Clusters.Objects.%s", spec.CanonicalName(parentEntity.Name)) + } + } + } + var sb strings.Builder + sb.WriteString(strcase.ToLowerCamel(name)) + sb.WriteRune('=') + if namespace != "" || objectGroup != "" || objectName != "" { + if namespace != "" { + sb.WriteString(namespace) + sb.WriteRune('.') + } + if objectGroup != "" { + sb.WriteString(objectGroup) + sb.WriteRune('.') + } + if objectName != "" { + sb.WriteString(objectName) + } + if valName != "" { + sb.WriteRune('.') + sb.WriteString(valName) + } else { + sb.WriteRune('(') + switch value := value.(type) { + case yaml.MapSlice: + var count int + for _, val := range value { + key, ok := val.Key.(string) + if !ok { + continue + } + if count > 0 { + sb.WriteString(", ") + } + sb.WriteString(key) + sb.WriteString("=") + sb.WriteString(string(pythonValueHelper(val.Value))) + count++ + } + default: + sb.WriteString(string(pythonValueHelper(value))) + } + sb.WriteRune(')') + } + return sb.String() + } + sb.WriteString(string(pythonValueHelper(value))) + return sb.String() + +} + +func commandArgHelper(test test, step testStep, name string) raymond.SafeString { + for _, arg := range step.Arguments.Values { + argName, value, ok := getArg(arg) + if !ok { + slog.Warn("unable to cast arg", slog.String("testId", test.ID), log.Type("type", arg)) + continue + } + if strings.EqualFold(name, argName) { + return pythonValueHelper(value) + } + } + return raymond.SafeString(fmt.Sprintf("unknown argument: %s", name)) +} + +func commandArgsHelper(spec *spec.Specification) func(test test, step testStep) raymond.SafeString { + return func(test test, step testStep) raymond.SafeString { + clusterName, cluster := getCluster(spec, &test, &step) + if cluster == nil { + return raymond.SafeString(fmt.Sprintf("error: unknown cluster: %s", clusterName)) + } + var command *matter.Command + for _, cmd := range cluster.Commands { + if strings.EqualFold(cmd.Name, step.Command) { + command = cmd + break + } + } + if command == nil { + slog.Warn("Unknown command in test", slog.String("testId", test.ID), slog.String("step", step.Label), slog.String("commandName", step.Command)) + } + var args []string + if len(step.Arguments.Values) > 0 { + for _, v := range step.Arguments.Values { + var name string + var value any + switch v := v.(type) { + case map[string]any: + var ok bool + name, value, ok = getArg(v) + if !ok { + continue + } + + case yaml.MapSlice: + n, ok := parse.ValueFromMapSlice(v, "name") + if !ok { + continue + } + name, ok = n.(string) + if !ok { + continue + } + value, ok = parse.ValueFromMapSlice(v, "value") + if !ok { + continue + } + } + if name == "" { + continue + } + var field *matter.Field + for _, f := range command.Fields { + if strings.EqualFold(name, f.Name) { + field = f + break + } + } + if field == nil { + slog.Warn("Unknown command field in test", slog.String("fieldName", name)) + } + args = append(args, commandArgValue(name, cluster, field, value)) + } + } + + return raymond.SafeString(strings.Join(args, ", ")) + } +} diff --git a/testing/generate/generate.go b/testing/generate/generate.go new file mode 100644 index 00000000..10cf43e0 --- /dev/null +++ b/testing/generate/generate.go @@ -0,0 +1,102 @@ +package generate + +import ( + "context" + "log/slog" + "path/filepath" + + "github.com/mailgun/raymond/v2" + "github.com/project-chip/alchemy/internal/pipeline" + "github.com/project-chip/alchemy/internal/text" + "github.com/project-chip/alchemy/matter/spec" + "github.com/project-chip/alchemy/testing/parse" +) + +type GeneratorOption func(g *PythonTestGenerator) +type PythonTestGenerator struct { + sdkRoot string + templateRoot string + overwrite bool + + spec *spec.Specification + picsLabels map[string]string +} + +func TemplateRoot(templateRoot string) func(*PythonTestGenerator) { + return func(g *PythonTestGenerator) { + g.templateRoot = templateRoot + } +} + +func Overwrite(overwrite bool) func(*PythonTestGenerator) { + return func(g *PythonTestGenerator) { + g.overwrite = overwrite + } +} + +func NewPythonTestGenerator(spec *spec.Specification, sdkRoot string, picsLabels map[string]string, options ...GeneratorOption) *PythonTestGenerator { + ptg := &PythonTestGenerator{spec: spec, sdkRoot: sdkRoot, picsLabels: picsLabels} + for _, o := range options { + o(ptg) + } + return ptg +} + +func (sp PythonTestGenerator) Name() string { + return "Generating test plans" +} + +func (sp PythonTestGenerator) Type() pipeline.ProcessorType { + return pipeline.ProcessorTypeIndividual +} + +func (sp *PythonTestGenerator) Process(cxt context.Context, input *pipeline.Data[*parse.Test], index int32, total int32) (outputs []*pipeline.Data[string], extras []*pipeline.Data[*parse.Test], err error) { + + outPath := sp.getPath(input.Path) + slog.Info("generating", "in", input.Path, "out", outPath) + + var test *test + test, err = sp.convert(input.Content, input.Path) + if err != nil { + slog.WarnContext(cxt, "Error converting test to model", slog.String("path", input.Path), slog.Any("error", err)) + err = nil + return + } + + if test.Config.Cluster == "" { + return + } + + var t *raymond.Template + t, err = sp.loadTemplate(sp.spec) + if err != nil { + return + } + variables := make(map[string]struct{}) + t.RegisterHelper("variable", variableHelper(variables)) + t.RegisterHelper("value", valueHelper(variables)) + tc := map[string]any{ + "test": test, + } + var out string + out, err = t.Exec(tc) + if err != nil { + return + } + outputs = append(outputs, pipeline.NewData(outPath, out)) + return +} + +func (sp *PythonTestGenerator) getPath(path string) string { + + path = getTestName(path) + path += ".py" + return filepath.Join(sp.sdkRoot, "src/python_testing", path) +} + +func getTestName(path string) string { + path = filepath.Base(path) + path = text.TrimCaseInsensitivePrefix(path, "test_") + path = text.TrimCaseInsensitiveSuffix(path, ".yaml") + return path +} diff --git a/testing/generate/helper.go b/testing/generate/helper.go new file mode 100644 index 00000000..604306aa --- /dev/null +++ b/testing/generate/helper.go @@ -0,0 +1,247 @@ +package generate + +import ( + "encoding/hex" + "fmt" + "log/slog" + "math" + "strconv" + "strings" + + "github.com/goccy/go-yaml" + "github.com/iancoleman/strcase" + "github.com/mailgun/raymond/v2" + "github.com/project-chip/alchemy/internal/text" + "github.com/project-chip/alchemy/matter/spec" +) + +func registerHelpers(t *raymond.Template, spec *spec.Specification) { + t.RegisterHelper("pics", picsHelper) + t.RegisterHelper("picsGuard", picsGuardHelper) + t.RegisterHelper("clusterIs", clusterIsHelper) + t.RegisterHelper("commandIs", commandIsHelper) + t.RegisterHelper("pythonValue", pythonValueHelper) + t.RegisterHelper("asUpperCamelCase", asUpperCamelCaseHelper) + t.RegisterHelper("clusterName", clusterNameHelper(spec)) + t.RegisterHelper("clusterVariable", clusterVariableHelper(spec)) + t.RegisterHelper("endpointVariable", endpointVariableHelper) + t.RegisterHelper("stepClusterName", stepClusterNameHelper(spec)) + t.RegisterHelper("commandArg", commandArgHelper) + t.RegisterHelper("commandArgs", commandArgsHelper(spec)) + t.RegisterHelper("statusError", statusErrorHelper) + t.RegisterHelper("octetString", octetStringHelper) + t.RegisterHelper("pythonString", pythonStringHelper) +} + +func clusterIsHelper(step testStep, is string, options *raymond.Options) string { + if step.Cluster == is { + return options.Fn() + } + return options.Inverse() +} + +func clusterNameHelper(sp *spec.Specification) func(test test) raymond.SafeString { + return func(test test) raymond.SafeString { + clusterName := test.Config.Cluster + _, ok := sp.ClustersByName[clusterName] + if !ok { + slog.Warn("Unknown cluster in test", slog.String("clusterName", clusterName)) + } + return raymond.SafeString(spec.CanonicalName(clusterName)) + } +} + +func stepClusterNameHelper(sp *spec.Specification) func(test test, step testStep) raymond.SafeString { + return func(test test, step testStep) raymond.SafeString { + clusterName := test.Config.Cluster + if step.Cluster != "" { + clusterName = step.Cluster + } + _, ok := sp.ClustersByName[clusterName] + if !ok { + slog.Warn("Unknown cluster in test", slog.String("clusterName", clusterName)) + } + return raymond.SafeString(spec.CanonicalName(clusterName)) + } +} + +func clusterVariableHelper(sp *spec.Specification) func(test test, step testStep) raymond.SafeString { + return func(test test, step testStep) raymond.SafeString { + if step.Cluster == "" || step.Cluster == test.Config.Cluster { + return raymond.SafeString("cluster") + } + clusterName := step.Cluster + _, ok := sp.ClustersByName[clusterName] + if !ok { + slog.Warn("Unknown cluster in test", slog.String("clusterName", clusterName)) + } + return raymond.SafeString("Clusters." + spec.CanonicalName(clusterName)) + } +} + +func endpointVariableHelper(test test, step testStep) raymond.SafeString { + if step.Endpoint != math.MaxUint64 { + return raymond.SafeString(strconv.FormatUint(step.Endpoint, 10)) + } + return raymond.SafeString("endpoint") +} + +func commandIsHelper(step testStep, is string, options *raymond.Options) string { + if step.Command == is { + return options.Fn() + } + return options.Inverse() +} + +func pythonValueHelper(value any) raymond.SafeString { + switch value := value.(type) { + case uint64: + return raymond.SafeString(strconv.FormatUint(value, 10)) + case string: + return raymond.SafeString(value) + case int64: + return raymond.SafeString(strconv.FormatInt(value, 10)) + case yaml.MapSlice: + var sb strings.Builder + sb.WriteRune('{') + var count int + for _, val := range value { + key, ok := val.Key.(string) + if !ok { + continue + } + if count > 0 { + sb.WriteString(", ") + } + sb.WriteString(key) + sb.WriteString(": ") + sb.WriteString(string(pythonValueHelper(val.Value))) + count++ + } + sb.WriteRune('}') + return raymond.SafeString(sb.String()) + case []uint64: + return numberArray(value, strconv.FormatUint) + case []int64: + return numberArray(value, strconv.FormatInt) + case []any: + var elements []string + for _, e := range value { + elements = append(elements, string(pythonValueHelper(e))) + } + return raymond.SafeString("[" + strings.Join(elements, ", ") + "]") + case nil: + return raymond.SafeString("None") + case bool: + if value { + return raymond.SafeString("True") + } + return raymond.SafeString("False") + default: + return raymond.SafeString(fmt.Sprintf("unknown pythonValue type: %T", value)) + } +} + +func numberArray[T any](value []T, formatter func(T, int) string) raymond.SafeString { + var sb strings.Builder + var count int + sb.WriteRune('[') + for _, v := range value { + if count > 0 { + sb.WriteString(", ") + } + sb.WriteString(formatter(v, 10)) + count++ + } + sb.WriteRune(']') + return raymond.SafeString(sb.String()) +} + +func statusErrorHelper(value string) raymond.SafeString { + return raymond.SafeString("Status." + strcase.ToCamel(value)) +} + +func asUpperCamelCaseHelper(value string) raymond.SafeString { + return raymond.SafeString(strcase.ToCamel(value)) +} + +func octetStringHelper(value string) raymond.SafeString { + if text.HasCaseInsensitivePrefix(value, "hex:") { + bytes, err := hex.DecodeString(text.TrimCaseInsensitivePrefix(value, "hex:")) + if err != nil { + slog.Warn("Error parsing hexadecimal value", slog.Any("error", err), slog.String("value", value)) + } else { + var hexstring strings.Builder + hexstring.WriteString("b'") + for _, b := range bytes { + hexstring.WriteRune('\\') + hexstring.WriteString(fmt.Sprintf("%x", b)) + } + hexstring.WriteRune('\'') + return raymond.SafeString(hexstring.String()) + } + } + return raymond.SafeString(`""`) +} + +func pythonStringHelper(s string) raymond.SafeString { + var sb strings.Builder + quoteCharacter := '\'' + if strings.ContainsRune(s, '\'') && !strings.ContainsRune(s, '"') { + quoteCharacter = '"' + } + sb.WriteRune(quoteCharacter) + for _, r := range s { + switch { + case r < ' ': + // Control characters + switch r { + // See https://www.w3schools.com/python/gloss_python_escape_characters.asp + case '\n': + sb.WriteString(`\n`) + case '\r': + sb.WriteString(`\r`) + case '\t': + sb.WriteString(`\t`) + case '\b': + sb.WriteString(`\b`) + case '\f': + sb.WriteString(`\f`) + default: + sb.WriteString(fmt.Sprintf(`\x%02x`, r)) + } + case strconv.IsPrint(r): + if r == '\\' || r == quoteCharacter { + sb.WriteRune('\\') + } + sb.WriteRune(r) + default: + sb.WriteString(fmt.Sprintf(`\x%02x`, r)) + } + } + sb.WriteRune(quoteCharacter) + return raymond.SafeString(sb.String()) +} + +func valueHelper(variables map[string]struct{}) func(value any) raymond.SafeString { + + return func(value any) raymond.SafeString { + switch value := value.(type) { + case string: + _, ok := variables[value] + if ok { + return raymond.SafeString(value) + } + return pythonStringHelper(value) + default: + return pythonValueHelper(value) + } + } +} + +func variableHelper(variables map[string]struct{}) func(variableName string) raymond.SafeString { + return func(variableName string) raymond.SafeString { + variables[variableName] = struct{}{} + return raymond.SafeString(variableName) + } +} diff --git a/testing/generate/model.go b/testing/generate/model.go new file mode 100644 index 00000000..49fc805d --- /dev/null +++ b/testing/generate/model.go @@ -0,0 +1,108 @@ +package generate + +import ( + "regexp" + "strings" + + "github.com/project-chip/alchemy/matter/types" + "github.com/project-chip/alchemy/testing/parse" + "github.com/project-chip/alchemy/testing/pics" +) + +type test struct { + parse.Test + //Name string + ID string + //Cluster string + PICSList []pics.Expression + Groups []*testGroup + + Variables []string + PICSAliases map[string]string + PICSAliasList [][]*picsAlias +} + +type testGroup struct { + Step string + Description string + Steps []*testStep +} + +type testStep struct { + parse.TestStep + Description []string + UserVerification []string + PICSSet pics.Expression +} + +var stepPattern = regexp.MustCompile(`(?s)^\s*[s|S]tep\s+([0-9a-zA-Z]+):\s*(.*)`) + +func (sp *PythonTestGenerator) convert(tst *parse.Test, path string) (t *test, err error) { + t = &test{ + ID: getTestName(path), + Test: *tst, + } + for _, tp := range tst.PICS { + var pe pics.Expression + pe, err = pics.ParseString(tp) + if err != nil { + return + } + t.PICSList = append(t.PICSList, pe) + } + var currentGroup *testGroup + for _, s := range tst.Tests { + ts := &testStep{ + TestStep: *s, + } + labelParts := stepPattern.FindStringSubmatch(s.Label) + var label, description string + if len(labelParts) > 0 { + label = labelParts[1] + description = labelParts[2] + } else { + description = s.Label + } + if len(label) > 0 { + if currentGroup == nil || label != currentGroup.Step { + currentGroup = &testGroup{Step: label, Description: description} + t.Groups = append(t.Groups, currentGroup) + } + } else if currentGroup == nil { + currentGroup = &testGroup{Step: label, Description: description} + } + if len(description) > 0 { + + ts.Description = strings.Split(description, "\n") + } + if len(s.Verification) > 0 { + ts.UserVerification = strings.Split(s.Verification, "\n") + } + ts.PICSSet, err = pics.ParseString(s.PICS) + if err != nil { + return + } + currentGroup.Steps = append(currentGroup.Steps, ts) + } + t.Variables = getVariables(t) + t.PICSAliases = sp.buildPicsMap(t) + picsAliases := buildPicsAliasList(t.PICSAliases) + var lastEntityType = types.EntityTypeUnknown + var entityAliases []*picsAlias + for _, pa := range picsAliases { + if pa.entityType != lastEntityType && len(entityAliases) > 0 { + t.PICSAliasList = append(t.PICSAliasList, entityAliases) + entityAliases = nil + } + entityAliases = append(entityAliases, pa) + label, ok := sp.picsLabels[pa.Pics] + if ok { + pa.Comments = strings.Split(label, "\n") + } + lastEntityType = pa.entityType + } + if len(entityAliases) > 0 { + t.PICSAliasList = append(t.PICSAliasList, entityAliases) + } + return +} diff --git a/testing/generate/pics.go b/testing/generate/pics.go new file mode 100644 index 00000000..5b29ac2e --- /dev/null +++ b/testing/generate/pics.go @@ -0,0 +1,223 @@ +package generate + +import ( + "fmt" + "log/slog" + "regexp" + "slices" + "strconv" + "strings" + + "github.com/mailgun/raymond/v2" + "github.com/project-chip/alchemy/matter" + "github.com/project-chip/alchemy/matter/types" + "github.com/project-chip/alchemy/testing/pics" +) + +func (sp *PythonTestGenerator) buildPicsMap(t *test) (aliases map[string]string) { + aliases = make(map[string]string) + clusters := make(map[string]*matter.Cluster) + for _, tp := range t.PICSList { + sp.buildMapForPics(tp, clusters, aliases) + } + for _, ts := range t.Groups { + for _, ts := range ts.Steps { + sp.buildMapForPics(ts.PICSSet, clusters, aliases) + } + } + return +} + +func (sp *PythonTestGenerator) buildMapForPics(exp pics.Expression, clusters map[string]*matter.Cluster, aliases map[string]string) { + switch exp := exp.(type) { + case *pics.PICSExpression: + _, ok := aliases[exp.PICS] + if ok { + return + } + aliases[exp.PICS] = sp.makePicsAlias(clusters, exp.PICS) + case *pics.LogicalExpression: + sp.buildMapForPics(exp.Left, clusters, aliases) + for _, re := range exp.Right { + sp.buildMapForPics(re, clusters, aliases) + } + } +} + +var picsPattern = regexp.MustCompile(`^([A-Z]+)\.([SC])\.([FACE])([0-9a-fA-F]+)(?:\.(Tx|Rsp))?$`) + +func (sp *PythonTestGenerator) makePicsAlias(clusters map[string]*matter.Cluster, p string) string { + parts := picsPattern.FindStringSubmatch(p) + if len(parts) < 5 { + return "" + } + iface := parts[2] + if !strings.EqualFold(iface, "S") { + // We don't handle aliases for client PICS + return "" + } + clusterPics := parts[1] + cluster, ok := clusters[clusterPics] + if !ok { + for c := range sp.spec.Clusters { + if strings.EqualFold(c.PICS, clusterPics) { + cluster = c + clusters[c.PICS] = c + break + } + } + } + if cluster == nil { + slog.Warn("Unable to find matching cluster for PICS", slog.String("pics", clusterPics)) + return "" + } + entityType := parts[3] + id, err := strconv.ParseUint(parts[4], 16, 64) + if err != nil { + slog.Warn("Error parsing PICS id", slog.String("id", parts[4]), slog.Any("error", err)) + return "" + } + switch entityType { + case "F": + if cluster.Features == nil { + slog.Warn("Cluster missing features", slog.String("clusterName", cluster.Name), slog.Uint64("Feature Bit", id)) + return "" + } + for _, b := range cluster.Features.Bits { + f := b.(*matter.Feature) + from, to, err := f.Bits() + if err != nil { + slog.Warn("Error parsing feature bits", slog.String("feature", f.Code), slog.Any("error", err)) + return "" + } + if from != to { + continue + } + if from == id { + return fmt.Sprintf("has%sFeature", matter.Case(f.Name())) + } + } + slog.Warn("Unable to find matching feature for PICS", slog.String("clusterName", cluster.Name), slog.String("pics", p)) + case "A": + for _, a := range cluster.Attributes { + if !a.ID.Valid() { + continue + } + if a.ID.Value() == id { + return fmt.Sprintf("has%sAttribute", matter.Case(a.Name)) + } + } + slog.Warn("Unable to find matching attribute for PICS", slog.String("clusterName", cluster.Name), slog.String("pics", p)) + case "C": + var direction matter.Interface + switch parts[5] { + case "Rsp": + direction = matter.InterfaceServer + case "Tx": + direction = matter.InterfaceClient + default: + slog.Warn("Unknown command direction while building aliases", slog.String("direction", parts[5])) + } + for _, c := range cluster.Commands { + if !c.ID.Valid() { + continue + } + if c.ID.Value() == id && c.Direction == direction { + return fmt.Sprintf("has%sCommand", matter.Case(c.Name)) + } + } + var commands []any + for _, c := range cluster.Commands { + commands = append(commands, slog.Group("command", slog.Uint64("id", c.ID.Value()), slog.String("direction", c.Direction.String()))) + } + slog.Warn("Unable to find matching command for PICS", slog.String("clusterName", cluster.Name), slog.String("pics", p), slog.Uint64("id", id), slog.String("direction", direction.String()), slog.Group("checked", commands...)) + + case "E": + for _, e := range cluster.Events { + if !e.ID.Valid() { + continue + } + if e.ID.Value() == id { + return fmt.Sprintf("has%sEvent", matter.Case(e.Name)) + } + } + slog.Warn("Unable to find matching event for PICS", slog.String("clusterName", cluster.Name), slog.String("pics", p)) + default: + slog.Warn("Unknown entity type while building aliases", slog.String("entityType", entityType)) + } + slog.Info("pics parts", slog.String("cluster", parts[1]), slog.String("interface", parts[2]), slog.String("entity", parts[3]), slog.String("id", parts[4])) + return "" +} + +type picsAlias struct { + entityType types.EntityType + Pics string + Alias string + Comments []string +} + +func buildPicsAliasList(picsAliases map[string]string) (aliases []*picsAlias) { + for pics, alias := range picsAliases { + if alias == "" { + continue + } + pa := &picsAlias{Pics: pics, Alias: alias} + if strings.HasSuffix(alias, "Feature") { + pa.entityType = types.EntityTypeFeature + } else if strings.HasSuffix(alias, "Command") { + pa.entityType = types.EntityTypeCommand + } else if strings.HasSuffix(alias, "Attribute") { + pa.entityType = types.EntityTypeAttribute + } else if strings.HasSuffix(alias, "Event") { + pa.entityType = types.EntityTypeEvent + } + aliases = append(aliases, pa) + } + if len(aliases) == 0 { + return + } + slices.SortStableFunc(aliases, func(a, b *picsAlias) int { + if a.entityType == b.entityType { + return strings.Compare(a.Alias, b.Alias) + } + switch a.entityType { + case types.EntityTypeFeature: + return -1 + case types.EntityTypeAttribute: + switch b.entityType { + case types.EntityTypeFeature: + return 1 + default: + return -1 + } + case types.EntityTypeCommand: + switch b.entityType { + case types.EntityTypeFeature, types.EntityTypeAttribute: + return 1 + default: + return -1 + } + case types.EntityTypeEvent: + return 1 + } + if a.entityType < b.entityType { + return -1 + } + return 1 + }) + return +} + +func picsHelper(pics []pics.Expression) raymond.SafeString { + ps := make([]string, 0, len(pics)) + for _, r := range pics { + ps = append(ps, r.PythonString()) + } + return raymond.SafeString(strings.Join(ps, ",")) +} + +func picsGuardHelper(exp pics.Expression, aliases map[string]string) raymond.SafeString { + var sb strings.Builder + exp.PythonBuilder(aliases, &sb) + return raymond.SafeString(sb.String()) +} diff --git a/testing/generate/template.go b/testing/generate/template.go new file mode 100644 index 00000000..586b4b8a --- /dev/null +++ b/testing/generate/template.go @@ -0,0 +1,41 @@ +package generate + +import ( + "embed" + "log/slog" + + "github.com/mailgun/raymond/v2" + "github.com/project-chip/alchemy/internal/handlebars" + "github.com/project-chip/alchemy/internal/pipeline" + "github.com/project-chip/alchemy/matter/spec" +) + +//go:embed templates +var templateFiles embed.FS + +var template pipeline.Once[*raymond.Template] + +func (sp *PythonTestGenerator) loadTemplate(spec *spec.Specification) (*raymond.Template, error) { + t, err := template.Do(func() (*raymond.Template, error) { + + ov := handlebars.NewOverlay(sp.templateRoot, templateFiles, "templates") + err := ov.Flush() + if err != nil { + slog.Error("Error flushing embedded templates", slog.Any("error", err)) + } + t, err := handlebars.LoadTemplate("{{> python_test}}", ov) + if err != nil { + return nil, err + } + + handlebars.RegisterCommonHelpers(t) + + registerHelpers(t, spec) + + return t, nil + }) + if err != nil { + return nil, err + } + return t.Clone(), nil +} diff --git a/testing/generate/templates/python_test.handlebars b/testing/generate/templates/python_test.handlebars new file mode 100644 index 00000000..f2b6e390 --- /dev/null +++ b/testing/generate/templates/python_test.handlebars @@ -0,0 +1,100 @@ +# +# Copyright (c) 2024 Project CHIP Authors +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# See https://github.com/project-chip/connectedhomeip/blob/master/docs/testing/python.md#defining-the-ci-test-arguments +# for details about the block below. +# +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --endpoint 1 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + +import copy +import logging +import random + +import chip.clusters as Clusters +from chip import ChipDeviceCtrl # Needed before chip.FabricAdmin +from chip.clusters import Globals +from chip.clusters.Types import NullValue +from chip.interaction_model import InteractionModelError, Status +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from mobly import asserts + +logger = logging.getLogger(__name__) + +cluster = Clusters.{{clusterName test}} + +class {{test.ID}}(MatterBaseTest): + + def desc_{{test.ID}}(self) -> str: + """Returns a description of this test""" + return {{quote test.Name}} + + def pics_{{test.ID}}(self): + """This function returns a list of PICS for this test case that must be True for the test to be run""" + return [{{pics test.PICSList}}] + + def steps_{{test.ID}}(self) -> list[TestStep]: + steps = [ +{{#each test.Groups}} +{{#if this.step}} + TestStep({{quote this.step}}, {{quote this.description}}), +{{/if}} +{{/each}} + ] + + return steps + + @ async_test_body + async def test_{{test.ID}}(self): + endpoint = self.matter_test_config.endpoint if self.matter_test_config.endpoint is not None else 1 + + {{> test/config/list test=test}} + +{{#each test.Variables}} + {{variable this}} = None +{{/each}} + +{{#each test.PICSAliasList}} +{{#each this}} +{{#each this.Comments}} + # {{raw this}} +{{/each}} + {{this.alias}} = self.check_pics({{quote this.pics}}) +{{/each}} + +{{/each}} +{{#each test.Groups}} + self.step({{quote this.step}}) + +{{#each this.Steps}} +{{#if this.PICSSet}} + if self.pics_guard({{picsGuard this.PICSSet test.PICSAliases}}): + {{> test/step test=test step=this}} + +{{else}} + {{> test/step test=test step=this}} + +{{/if}} +{{/each}} +{{/each}} +if __name__ == "__main__": + default_matter_test_main() diff --git a/testing/generate/templates/test/commands/cluster_arg.handlebars b/testing/generate/templates/test/commands/cluster_arg.handlebars new file mode 100644 index 00000000..2e218002 --- /dev/null +++ b/testing/generate/templates/test/commands/cluster_arg.handlebars @@ -0,0 +1,3 @@ +{{#ifSet step.cluster ~}} +{{clusterVariable test step ~}} +{{else}}cluster{{/ifSet}} \ No newline at end of file diff --git a/testing/generate/templates/test/commands/command.handlebars b/testing/generate/templates/test/commands/command.handlebars new file mode 100644 index 00000000..5a5981f2 --- /dev/null +++ b/testing/generate/templates/test/commands/command.handlebars @@ -0,0 +1,3 @@ +{{#if step.Command ~}} +await self.send_single_cmd(cmd=Clusters.Objects.{{stepClusterName test step}}.Commands.{{step.Command}}({{commandArgs test step}})) +{{/if}} diff --git a/testing/generate/templates/test/commands/equality.handlebars b/testing/generate/templates/test/commands/equality.handlebars new file mode 100644 index 00000000..410fc673 --- /dev/null +++ b/testing/generate/templates/test/commands/equality.handlebars @@ -0,0 +1 @@ +asserts.assert_equal({{commandArg test step "Value1"}}, {{commandArg test step "Value2"}}) diff --git a/testing/generate/templates/test/commands/read_attribute.handlebars b/testing/generate/templates/test/commands/read_attribute.handlebars new file mode 100644 index 00000000..08d98d5f --- /dev/null +++ b/testing/generate/templates/test/commands/read_attribute.handlebars @@ -0,0 +1,11 @@ +{{#if step.Response.Error ~}} +await self.read_single_attribute_expect_error(endpoint={{endpointVariable test step}}, cluster={{> test/commands/cluster_arg test=test step=step}}, attribute=cluster.Attributes.{{step.Attribute}}) +{{else ~}} +{{#if step.Response.SaveAs ~}} +{{variable step.Response.SaveAs}} = {{else if step.Response.Constraints ~}} +val = {{else if step.Response.Value ~}}val = {{/if ~}} +await self.read_single_attribute_check_success(endpoint={{endpointVariable test step}}, cluster={{> test/commands/cluster_arg test=test step=step}}, attribute=cluster.Attributes.{{step.Attribute ~}} +{{#if step.FabricFiltered ~}} +fabricFiltered=True{{/if}}) +{{/if}} +{{> test/constraints/list test=test step=step}} \ No newline at end of file diff --git a/testing/generate/templates/test/commands/response_value.handlebars b/testing/generate/templates/test/commands/response_value.handlebars new file mode 100644 index 00000000..ce911555 --- /dev/null +++ b/testing/generate/templates/test/commands/response_value.handlebars @@ -0,0 +1 @@ +{{#if step.Response.SaveAs}}{{step.Response.SaveAs}}{{else}}val{{/if}} \ No newline at end of file diff --git a/testing/generate/templates/test/commands/system.handlebars b/testing/generate/templates/test/commands/system.handlebars new file mode 100644 index 00000000..e69de29b diff --git a/testing/generate/templates/test/commands/user_prompt.handlebars b/testing/generate/templates/test/commands/user_prompt.handlebars new file mode 100644 index 00000000..eda8a094 --- /dev/null +++ b/testing/generate/templates/test/commands/user_prompt.handlebars @@ -0,0 +1,6 @@ +pass +# TODO: Rewrite this user prompt test +# +{{#each step.userVerification}} +# {{raw this}} +{{/each}} diff --git a/testing/generate/templates/test/commands/write_attribute.handlebars b/testing/generate/templates/test/commands/write_attribute.handlebars new file mode 100644 index 00000000..18082368 --- /dev/null +++ b/testing/generate/templates/test/commands/write_attribute.handlebars @@ -0,0 +1,3 @@ +{{#if step.Response.Error}}status = {{/if}}await self.write_single_attribute(attribute_value={{clusterVariable test step}}.Attributes.{{step.Attribute}}({{value step.arguments.value}}), cluster={{clusterVariable test step}}, endpoint_id={{endpointVariable test step}}{{#if step.Response.Error}}, expect_success=False{{/if}}) +{{#if step.Response.Error}}asserts.assert_equal(status, {{statusError step.Response.Error}}) +{{/if}} \ No newline at end of file diff --git a/testing/generate/templates/test/config/list.handlebars b/testing/generate/templates/test/config/list.handlebars new file mode 100644 index 00000000..ec629d6e --- /dev/null +++ b/testing/generate/templates/test/config/list.handlebars @@ -0,0 +1,3 @@ +{{#each test.Config.Extras}} +{{variable this.key}} = {{> test/config/value value=this.value}} +{{/each}} \ No newline at end of file diff --git a/testing/generate/templates/test/config/value.handlebars b/testing/generate/templates/test/config/value.handlebars new file mode 100644 index 00000000..7f2eaf12 --- /dev/null +++ b/testing/generate/templates/test/config/value.handlebars @@ -0,0 +1,10 @@ +{{#ifSet value.defaultValue ~}} +{{#ifEqual value.type "octet_string" ~}} +{{octetString value.defaultValue ~}} +{{else ifEqual value.type "char_string" ~}} +{{pythonString value.defaultValue ~}} +{{else ~}} +{{pythonValue value.defaultValue ~}} +{{/ifEqual ~}} +{{else ~}} +None{{/ifSet}} \ No newline at end of file diff --git a/testing/generate/templates/test/constraints/any_of.handlebars b/testing/generate/templates/test/constraints/any_of.handlebars new file mode 100644 index 00000000..6d7e1dec --- /dev/null +++ b/testing/generate/templates/test/constraints/any_of.handlebars @@ -0,0 +1,3 @@ +{{#ifSet step.Response.Constraints.AnyOf}} +asserts.assert_in({{> test/commands/response_value step=step}}, {{pythonValue step.Response.Constraints.AnyOf}}) +{{/ifSet}} \ No newline at end of file diff --git a/testing/generate/templates/test/constraints/contains.handlebars b/testing/generate/templates/test/constraints/contains.handlebars new file mode 100644 index 00000000..d92d6da6 --- /dev/null +++ b/testing/generate/templates/test/constraints/contains.handlebars @@ -0,0 +1,3 @@ +{{#if step.Response.Constraints.Contains}} +asserts.assert_true(set({{pythonValue step.Response.Constraints.Contains}}).issubset(set({{> test/commands/response_value step=step}}))) +{{/if}} \ No newline at end of file diff --git a/testing/generate/templates/test/constraints/has_masks_clear.handlebars b/testing/generate/templates/test/constraints/has_masks_clear.handlebars new file mode 100644 index 00000000..937c4b1a --- /dev/null +++ b/testing/generate/templates/test/constraints/has_masks_clear.handlebars @@ -0,0 +1,3 @@ +{{#if step.Response.Constraints.HasMasksClear}} +asserts.assert_true(all([({{> test/commands/response_value step=step}} & mask) == 0 for mask in {{pythonValue step.Response.Constraints.HasMasksClear}}]) +{{/if}} \ No newline at end of file diff --git a/testing/generate/templates/test/constraints/has_masks_set.handlebars b/testing/generate/templates/test/constraints/has_masks_set.handlebars new file mode 100644 index 00000000..3fb03809 --- /dev/null +++ b/testing/generate/templates/test/constraints/has_masks_set.handlebars @@ -0,0 +1,3 @@ +{{#if step.Response.Constraints.HasMasksSet}} +asserts.assert_true(all([({{> test/commands/response_value step=step}} & mask) != 0 for mask in {{pythonValue step.Response.Constraints.HasMasksSet}}]) +{{/if}} \ No newline at end of file diff --git a/testing/generate/templates/test/constraints/list.handlebars b/testing/generate/templates/test/constraints/list.handlebars new file mode 100644 index 00000000..048893c6 --- /dev/null +++ b/testing/generate/templates/test/constraints/list.handlebars @@ -0,0 +1,7 @@ +{{> test/constraints/min_value test=test step=step}} +{{> test/constraints/max_value test=test step=step}} +{{> test/constraints/value test=test step=step}} +{{> test/constraints/any_of test=test step=step}} +{{> test/constraints/has_masks_set test=test step=step}} +{{> test/constraints/has_masks_clear test=test step=step}} +{{> test/constraints/contains test=test step=step}} \ No newline at end of file diff --git a/testing/generate/templates/test/constraints/max_value.handlebars b/testing/generate/templates/test/constraints/max_value.handlebars new file mode 100644 index 00000000..90019146 --- /dev/null +++ b/testing/generate/templates/test/constraints/max_value.handlebars @@ -0,0 +1,3 @@ +{{#ifSet step.Response.Constraints.MaxValue}} +asserts.assert_less_equal({{> test/commands/response_value step=step}}, {{pythonValue step.Response.Constraints.MaxValue}}) +{{/ifSet}} \ No newline at end of file diff --git a/testing/generate/templates/test/constraints/min_value.handlebars b/testing/generate/templates/test/constraints/min_value.handlebars new file mode 100644 index 00000000..ce9cc85a --- /dev/null +++ b/testing/generate/templates/test/constraints/min_value.handlebars @@ -0,0 +1,3 @@ +{{#ifSet step.Response.Constraints.MinValue}} +asserts.assert_greater_equal({{> test/commands/response_value step=step}}, {{pythonValue step.Response.Constraints.MinValue}}) +{{/ifSet}} \ No newline at end of file diff --git a/testing/generate/templates/test/constraints/value.handlebars b/testing/generate/templates/test/constraints/value.handlebars new file mode 100644 index 00000000..444e390b --- /dev/null +++ b/testing/generate/templates/test/constraints/value.handlebars @@ -0,0 +1,3 @@ +{{#ifSet step.Response.Value}} +asserts.assert_equal({{> test/commands/response_value step=step}}, {{pythonValue step.Response.Value}}) +{{/ifSet}} \ No newline at end of file diff --git a/testing/generate/templates/test/step.handlebars b/testing/generate/templates/test/step.handlebars new file mode 100644 index 00000000..e60a54ba --- /dev/null +++ b/testing/generate/templates/test/step.handlebars @@ -0,0 +1,46 @@ +{{#if step.description}} +{{#each step.description}} +# {{raw this}} +{{/each}} +{{/if}} +{{#if step.comments}} +{{#each step.comments}} +# {{raw this}} +{{/each}} +{{/if}} +{{#if step.disabled}} +# Disabled +pass +{{else}} +{{#if step.userVerification}} +{{> test/commands/user_prompt step=step}} +{{else}} +{{#clusterIs step "DelayCommands"}} +# Delay Command +{{else clusterIs step "EqualityCommands"}} +{{> test/commands/equality test=test step=step}} +{{else clusterIs step "AnyCommands"}} +# TODO: Any Commands +{{else clusterIs step "CommissionerCommands"}} +# TODO: Commissioner Commands +{{else clusterIs step "SystemCommands"}} +# TODO: System Commands +{{else}} +{{#commandIs step "readAttribute"}} +{{> test/commands/read_attribute test=test step=step}} +{{else commandIs step "writeAttribute"}} +{{> test/commands/write_attribute test=test step=step}} +{{else commandIs step "UserPrompt"}} +{{> test/commands/user_prompt step=step}} +{{else commandIs step "readEvent"}} +# TODO: Read Event +{{else commandIs step "waitForReport"}} +# TODO: Wait For Report +{{else commandIs step "subscribeAttribute"}} +# TODO: Subscribe Attribute +{{else}} +{{> test/commands/command test=test step=step}} +{{/commandIs}} +{{/clusterIs}} +{{/if}} +{{/if}} \ No newline at end of file diff --git a/testing/generate/variables.go b/testing/generate/variables.go new file mode 100644 index 00000000..7d2b58d1 --- /dev/null +++ b/testing/generate/variables.go @@ -0,0 +1,23 @@ +package generate + +import ( + "slices" +) + +func getVariables(t *test) []string { + var variables []string + variableNames := make(map[string]struct{}) + for _, s := range t.Groups { + for _, s := range s.Steps { + if s.Response.SaveAs != "" { + if _, ok := variableNames[s.Response.SaveAs]; !ok { + variableNames[s.Response.SaveAs] = struct{}{} + variables = append(variables, s.Response.SaveAs) + } + } + } + } + + slices.Sort(variables) + return variables +} diff --git a/testing/parse/model.go b/testing/parse/model.go new file mode 100644 index 00000000..a8372e1a --- /dev/null +++ b/testing/parse/model.go @@ -0,0 +1,266 @@ +package parse + +import ( + "fmt" + "log/slog" + "math" + + "github.com/goccy/go-yaml" + "github.com/project-chip/alchemy/internal/log" +) + +type Test struct { + Path string `yaml:"-"` + + Name string `yaml:"name,omitempty"` + PICS []string `yaml:"PICS,omitempty"` + + Config TestConfig `yaml:"config,omitempty"` + Tests []*TestStep `yaml:"tests,omitempty"` + + Extras yaml.MapSlice +} + +func (t *Test) UnmarshalYAML(unmarshal func(interface{}) error) (err error) { + var yt yaml.MapSlice + if err = unmarshal(&yt); err != nil { + return err + } + t.Name, yt = extractValue[string](yt, "name") + var ok bool + t.PICS, yt, ok = tryExtractArray[string](yt, "PICS") + if !ok { + slog.Warn("unable to parse pics array", slog.String("path", t.Path)) + } + + var config yaml.MapSlice + config, yt = extractValue[yaml.MapSlice](yt, "config") + if config != nil { + err = t.Config.UnmarshalMapSlice(config) + if err != nil { + return + } + } + var tests []any + tests, yt = extractValue[[]any](yt, "tests") + for _, test := range tests { + switch test := test.(type) { + case yaml.MapSlice: + ts := TestStep{Parent: t} + err = ts.UnmarshalMapSlice(test) + if err != nil { + return + } + t.Tests = append(t.Tests, &ts) + default: + slog.Info("unknown test type!", slog.String("val", fmt.Sprintf("%T", test))) + } + } + if len(yt) > 0 { + t.Extras = make(yaml.MapSlice, len(yt)) + copy(t.Extras, yt) + } + + return nil +} + +type TestConfig struct { + NodeID uint64 `yaml:"nodeId,omitempty"` + Cluster string `yaml:"cluster,omitempty"` + Endpoint uint64 `yaml:"endpoint,omitempty"` + Timeout uint64 `yaml:"timeout,omitempty"` + CatalogVendorId TestConfigValue `yaml:"catalogVendorId,omitempty"` + ApplicationId TestConfigValue `yaml:"applicationId,omitempty"` + Payload any `yaml:"payload,omitempty"` + Discriminator TestConfigValue `yaml:"discriminator,omitempty"` + WaitAfterCommissioning TestConfigValue `yaml:"waitAfterCommissioning,omitempty"` + PakeVerifier TestConfigValue `yaml:"PakeVerifier,omitempty"` + + Extras yaml.MapSlice +} + +func (tc *TestConfig) UnmarshalMapSlice(c yaml.MapSlice) error { + tc.NodeID, c = extractValue[uint64](c, "nodeId") + tc.Cluster, c = extractValue[string](c, "cluster") + tc.Endpoint, c = extractValue[uint64](c, "endpoint") + tc.Timeout, c = extractValue[uint64](c, "timeout") + tc.CatalogVendorId, c = extractValue[TestConfigValue](c, "catalogVendorId") + tc.ApplicationId, c = extractValue[TestConfigValue](c, "applicationId") + tc.Discriminator, c = extractValue[TestConfigValue](c, "discriminator") + tc.WaitAfterCommissioning, c = extractValue[TestConfigValue](c, "waitAfterCommissioning") + + if len(c) > 0 { + tc.Extras = make(yaml.MapSlice, 0, len(c)) + for _, v := range c { + if ms, ok := v.Value.(yaml.MapSlice); ok { + var tcv TestConfigValue + err := tcv.UnmarshalMapSlice(ms) + if err != nil { + return err + } + tc.Extras = append(tc.Extras, yaml.MapItem{Key: v.Key, Value: tcv}) + } + } + } + return nil +} + +type TestConfigValue struct { + Type string `yaml:"type,omitempty"` + DefaultValue any `yaml:"defaultValue,omitempty"` +} + +func (tc *TestConfigValue) UnmarshalMapSlice(c yaml.MapSlice) error { + tc.Type, c = extractValue[string](c, "type") + tc.DefaultValue, _ = extractValue[any](c, "defaultValue") + return nil +} + +type TestStep struct { + Parent *Test `yaml:"-"` + + Label string `yaml:"label,omitempty"` + Comments []string `yaml:"-"` + PICS string `yaml:"PICS,omitempty"` + Cluster string `yaml:"cluster,omitempty"` + Endpoint uint64 `yaml:"endpoint,omitempty"` + Command string `yaml:"command,omitempty"` + Attribute string `yaml:"attribute,omitempty"` // handled + Verification string `yaml:"verification,omitempty"` // handled + Arguments TestArguments `yaml:"arguments,omitempty"` + Disabled bool `yaml:"disabled,omitempty"` // handled + FabricFiltered bool `yaml:"fabricFiltered,omitempty"` //handled + Response StepResponse `yaml:"response,omitempty"` + TimedInteractionTimeoutMs uint64 `yaml:"timedInteractionTimeoutMs,omitempty"` + Event string `yaml:"event,omitempty"` + EventNumber string `yaml:"eventNumber,omitempty"` + MaxInterval uint64 `yaml:"maxInterval,omitempty"` + MinInterval uint64 `yaml:"minInterval,omitempty"` + + Extras yaml.MapSlice +} + +func (ts *TestStep) UnmarshalMapSlice(c yaml.MapSlice) error { + ts.Label, c = extractValue[string](c, "label") + ts.PICS, c = extractValue[string](c, "PICS") + ts.Cluster, c = extractValue[string](c, "cluster") + ts.Endpoint, c = extractValue[uint64](c, "endpoint", math.MaxUint64) + ts.Command, c = extractValue[string](c, "command") + ts.Attribute, c = extractValue[string](c, "attribute") + ts.Verification, c = extractValue[string](c, "verification") + ts.Arguments, c = extractValue[TestArguments](c, "arguments") + ts.Disabled, c = extractValue[bool](c, "disabled") + ts.FabricFiltered, c = extractValue[bool](c, "fabricFiltered") + var ok bool + ts.Response, c, ok = tryExtractValue[StepResponse](c, "response") + if !ok { + var response []any + response, c, ok = tryExtractArrayAny(c, "response") + if ok { + for _, r := range response { + slog.Info("Response value", slog.String("path", ts.Parent.Path), log.Type("type", r)) + } + } + } + ts.TimedInteractionTimeoutMs, c = extractValue[uint64](c, "timedInteractionTimeoutMs") + ts.Event, c = extractValue[string](c, "event") + ts.EventNumber, c = extractValue[string](c, "eventNumber") + ts.MaxInterval, c = extractValue[uint64](c, "maxInterval") + ts.MinInterval, c = extractValue[uint64](c, "minInterval") + + if len(c) > 0 { + ts.Extras = make(yaml.MapSlice, len(c)) + copy(ts.Extras, c) + for _, extra := range c { + slog.Info("Test step has extra value", slog.Any("key", extra.Key), slog.Any("value", extra.Value)) + } + } + return nil +} + +type TestArguments struct { + Value any `yaml:"value,omitempty"` + Values []any `yaml:"values,omitempty"` +} + +func (ta *TestArguments) UnmarshalMapSlice(c yaml.MapSlice) error { + ta.Value, c = extractValue[any](c, "value") + ta.Values, _ = extractArrayAny(c, "values") + return nil +} + +type TestArgumentValue struct { + Name string `yaml:"name,omitempty"` + Value string `yaml:"value,omitempty"` +} + +type StepResponse struct { + SaveAs string `yaml:"saveAs,omitempty"` + Error string `yaml:"error,omitempty"` + Value any `yaml:"value,omitempty"` + Values []any `yaml:"values,omitempty"` + Constraints *StepResponseConstraints `yaml:"constraints,omitempty"` + + Extras yaml.MapSlice +} + +func (sr *StepResponse) UnmarshalMapSlice(c yaml.MapSlice) error { + sr.SaveAs, c = extractValue[string](c, "saveAs") + sr.Error, c = extractValue[string](c, "error") + sr.Value, c = extractValue[any](c, "value") + sr.Values, c = extractArrayAny(c, "values") + + sr.Constraints, c = extractObject[StepResponseConstraints](c, "constraints") + + if len(c) > 0 { + sr.Extras = make(yaml.MapSlice, len(c)) + copy(sr.Extras, c) + for _, extra := range c { + slog.Info("Test step response has extra value", slog.Any("key", extra.Key), slog.Any("value", extra.Value)) + } + } + return nil +} + +type StepResponseConstraints struct { + Type string `yaml:"type,omitempty"` + // MinLength any `yaml:"minLength,omitempty"` + // MaxLength any `yaml:"maxLength,omitempty"` + MinValue any `yaml:"minValue,omitempty"` + MaxValue any `yaml:"maxValue,omitempty"` + // NotValue any `yaml:"notValue,omitempty"` + // HasValue bool `yaml:"hasValue,omitempty"` + HasMasksSet []uint64 `yaml:"hasMasksSet,omitempty"` + HasMasksClear []uint64 `yaml:"hasMasksClear,omitempty"` + Contains any `yaml:"contains,omitempty"` + AnyOf any `yaml:"anyOf,omitempty"` + //Excludes []uint64 `yaml:"excludes,omitempty"` + + Extras yaml.MapSlice +} + +func (src *StepResponseConstraints) UnmarshalMapSlice(c yaml.MapSlice) error { + src.Type, c = extractValue[string](c, "type") + //src.MinLength = extractValue[any](c, "minLength") + //src.MaxLength = extractValue[any](c, "maxLength") + src.MinValue, c = extractValue[any](c, "minValue") + src.MaxValue, c = extractValue[any](c, "maxValue") + //src.NotValue = extractValue[any](c, "notValue") + //src.HasValue = extractValue[bool](c, "hasValue") + src.HasMasksSet, c = extractArray[uint64](c, "hasMasksSet") + src.HasMasksClear, c = extractArray[uint64](c, "hasMasksClear") + src.Contains, c = extractValue[any](c, "contains") + src.AnyOf, c = extractValue[any](c, "anyOf") + //src.Excludes = extractArray[uint64](c, "excludes") + // + if len(c) > 0 { + src.Extras = make(yaml.MapSlice, len(c)) + copy(src.Extras, c) + for _, extra := range c { + slog.Info("Test step response constraints has extra value", slog.Any("key", extra.Key), slog.Any("value", extra.Value)) + } + + } + + return nil +} diff --git a/testing/parse/parse.go b/testing/parse/parse.go new file mode 100644 index 00000000..991ec8eb --- /dev/null +++ b/testing/parse/parse.go @@ -0,0 +1,76 @@ +package parse + +import ( + "context" + "log/slog" + "os" + "path/filepath" + "regexp" + "strconv" + + "github.com/goccy/go-yaml" + "github.com/project-chip/alchemy/internal/pipeline" +) + +type TestYamlParser struct { + rootPath string +} + +func NewTestYamlParser(rootPath string) (TestYamlParser, error) { + if !filepath.IsAbs(rootPath) { + var err error + rootPath, err = filepath.Abs(rootPath) + if err != nil { + return TestYamlParser{}, err + } + } + return TestYamlParser{rootPath: rootPath}, nil +} + +func (p TestYamlParser) Name() string { + return "Parsing YAML tests" +} + +func (p TestYamlParser) Type() pipeline.ProcessorType { + return pipeline.ProcessorTypeIndividual +} + +func (p TestYamlParser) Process(cxt context.Context, input *pipeline.Data[struct{}], index int32, total int32) (outputs []*pipeline.Data[*Test], extras []*pipeline.Data[struct{}], err error) { + var r []byte + r, err = os.ReadFile(input.Path) + if err != nil { + return + } + t := Test{Path: input.Path} + cm := yaml.CommentMap{} + err = yaml.UnmarshalWithOptions(r, &t, yaml.DisallowUnknownField(), yaml.CommentToMap(cm), yaml.UseOrderedMap()) + if err != nil { + slog.WarnContext(cxt, "Failed parsing test YAML", slog.String("path", input.Path), slog.Any("error", err)) + err = nil + return + } + commentPattern := regexp.MustCompile(`\$\.tests\[([0-9]+)\]\.label`) + for key, c := range cm { + comments := commentPattern.FindStringSubmatch(key) + if len(comments) == 0 { + continue + } + index, err := strconv.Atoi(comments[1]) + if err != nil { + continue + } + if len(t.Tests) <= index { + continue + } + step := t.Tests[index] + for _, comment := range c { + step.Comments = append(step.Comments, comment.Texts...) + } + } + outputs = append(outputs, pipeline.NewData(input.Path, &t)) + return +} + +func stringUnmarshaller(s *string, b []byte) error { + return nil +} diff --git a/testing/parse/pics.go b/testing/parse/pics.go new file mode 100644 index 00000000..52c915d9 --- /dev/null +++ b/testing/parse/pics.go @@ -0,0 +1,51 @@ +package parse + +import ( + "fmt" + "log/slog" + "os" + "path/filepath" + + "github.com/goccy/go-yaml" +) + +type picsFile struct { + Name string `yaml:"name,omitempty"` + Entries []picsEntry `yaml:"PICS,omitempty"` +} + +type picsEntry struct { + ID string `yaml:"id,omitempty"` + Label string `yaml:"label,omitempty"` +} + +func LoadPICSLabels(sdkRoot string) (labels map[string]string, err error) { + + path := filepath.Join(sdkRoot, "src/app/tests/suites/certification/PICS.yaml") + + var r []byte + r, err = os.ReadFile(path) + if err != nil { + return + } + + var t picsFile + err = yaml.UnmarshalWithOptions(r, &t, yaml.DisallowUnknownField()) + if err != nil { + err = fmt.Errorf("error parsing %s: %w", path, err) + return + } + + labels = make(map[string]string) + + for _, e := range t.Entries { + _, ok := labels[e.ID] + if ok { + slog.Warn("duplicate PICS id", slog.String("id", e.ID)) + continue + } + labels[e.ID] = e.Label + } + + return +} diff --git a/testing/parse/yaml.go b/testing/parse/yaml.go new file mode 100644 index 00000000..85f31d1b --- /dev/null +++ b/testing/parse/yaml.go @@ -0,0 +1,166 @@ +package parse + +import ( + "fmt" + "log/slog" + "slices" + + "github.com/goccy/go-yaml" +) + +type mapSliceUnmarshaller interface { + UnmarshalMapSlice(c yaml.MapSlice) error +} + +func deleteMapItem(val yaml.MapSlice, key string) yaml.MapSlice { + return slices.DeleteFunc(val, func(mi yaml.MapItem) bool { + if mi, ok := mi.Key.(string); ok { + return mi == key + } + return false + }) +} + +func extractValue[T any](val yaml.MapSlice, key string, defaultValue ...T) (value T, out yaml.MapSlice) { + value, out, _ = tryExtractValue(val, key, defaultValue...) + return +} + +func tryExtractValue[T any](val yaml.MapSlice, key string, defaultValue ...T) (value T, out yaml.MapSlice, ok bool) { + out = val + var v any + v, ok = ValueFromMapSlice(val, key) + if !ok { + if len(defaultValue) > 0 { + value = defaultValue[0] + ok = true + } + return + } + switch v := v.(type) { + case T: + value = v + out = deleteMapItem(val, key) + return + case yaml.MapSlice: + var u mapSliceUnmarshaller + if u, ok = any(&value).(mapSliceUnmarshaller); ok { + err := u.UnmarshalMapSlice(v) + if err == nil { + out = deleteMapItem(val, key) + return + } + ok = false + } + default: + ok = false + } + if !ok { + slog.Info("unable to cast YAML value", slog.String("key", key), slog.String("expected", fmt.Sprintf("%T", value)), slog.String("got", fmt.Sprintf("%T", v))) + } + return +} + +func ValueFromMapSlice(val yaml.MapSlice, key string) (v any, ok bool) { + for _, si := range val { + switch sik := si.Key.(type) { + case string: + if sik == key { + v = si.Value + ok = true + return + } + } + } + return +} + +func extractObject[T any](val yaml.MapSlice, key string) (value *T, out yaml.MapSlice) { + out = val + v, ok := ValueFromMapSlice(val, key) + if !ok { + return + } + switch v := v.(type) { + case T: + value = &v + out = deleteMapItem(val, key) + return + case yaml.MapSlice: + value = new(T) + if u, ok := any(value).(mapSliceUnmarshaller); ok { + err := u.UnmarshalMapSlice(v) + if err == nil { + out = deleteMapItem(val, key) + return + } + } + } + slog.Info("unable to cast YAML value", slog.String("key", key), slog.String("expected", fmt.Sprintf("%T", value)), slog.String("got", fmt.Sprintf("%T", v))) + return +} + +func extractArray[T any](val yaml.MapSlice, key string) (value []T, out yaml.MapSlice) { + value, out, _ = tryExtractArray[T](val, key) + return +} + +func tryExtractArray[T any](val yaml.MapSlice, key string) (value []T, out yaml.MapSlice, ok bool) { + out = val + + var v any + v, ok = ValueFromMapSlice(val, key) + if !ok { + return + } + switch v := v.(type) { + case T: + value = append(value, v) + out = deleteMapItem(val, key) + return + case []T: + value = v + out = deleteMapItem(val, key) + return + case []any: + for _, v := range v { + var el T + el, ok = v.(T) + if ok { + value = append(value, el) + } else { + slog.Info("unable to cast YAML array value element", slog.String("key", key), slog.String("expected", fmt.Sprintf("%T", el)), slog.String("got", fmt.Sprintf("%T", v))) + return + } + } + out = deleteMapItem(val, key) + return + default: + ok = false + slog.Info("unable to cast YAML array value", slog.String("key", key), slog.String("expected", fmt.Sprintf("%T", value)), slog.String("got", fmt.Sprintf("%T", v))) + return + } +} + +func extractArrayAny(val yaml.MapSlice, key string) (value []any, out yaml.MapSlice) { + value, out, _ = tryExtractArrayAny(val, key) + return +} + +func tryExtractArrayAny(val yaml.MapSlice, key string) (value []any, out yaml.MapSlice, ok bool) { + out = val + var v any + v, ok = ValueFromMapSlice(val, key) + if !ok { + return + } + switch v := v.(type) { + case []any: + value = v + out = deleteMapItem(val, key) + return + } + slog.Info("unable to cast YAML any array value", slog.String("key", key), slog.String("expected", fmt.Sprintf("%T", value)), slog.String("got", fmt.Sprintf("%T", v))) + ok = false + return +} diff --git a/testing/pics/expression.go b/testing/pics/expression.go new file mode 100644 index 00000000..3db337bd --- /dev/null +++ b/testing/pics/expression.go @@ -0,0 +1,9 @@ +package pics + +import "strings" + +type Expression interface { + String() string + PythonString() string + PythonBuilder(aliases map[string]string, builder *strings.Builder) +} diff --git a/testing/pics/generate.go b/testing/pics/generate.go new file mode 100644 index 00000000..4b1b7f97 --- /dev/null +++ b/testing/pics/generate.go @@ -0,0 +1,23 @@ +//go:generate go run generate.go +//go:build generate + +package main + +import ( + "log/slog" + "os" + + "github.com/project-chip/alchemy/internal/generate" +) + +func main() { + slog.Info("Generating PICS parser...") + err := generate.Parser("grammar/grammar.json", false, nil) + if err != nil { + slog.Error("error generating PICS parser", slog.Any("error", err)) + os.Exit(1) + return + } + os.Exit(0) + return +} diff --git a/testing/pics/grammar/grammar.json b/testing/pics/grammar/grammar.json new file mode 100644 index 00000000..a431b7a0 --- /dev/null +++ b/testing/pics/grammar/grammar.json @@ -0,0 +1,11 @@ +{ + "grammars": [ + { + "path": "parser.go", + "files": [ + "pics.peg", + "logical.peg" + ] + } + ] +} diff --git a/testing/pics/grammar/logical.peg b/testing/pics/grammar/logical.peg new file mode 100644 index 00000000..4a320080 --- /dev/null +++ b/testing/pics/grammar/logical.peg @@ -0,0 +1,56 @@ + +LogicalTerm <- _ not:'!'? '(' _ eq:Logical _ ')' _ { + debug("matched logical term %s\n", string(c.text)) + if not != nil { + switch eq := eq.(type) { + case *LogicalExpression: + eq.Not = true + } + } + return eq, nil +} / _ eq:PICSOp _ { + debug("matched logical term %s\n", string(c.text)) + return eq, nil +} + +Logical <- Or + +Or <- _ left:And _ right:OrOp* { + debug("matched OR %s\n", string(c.text)) + if right == nil { + return left, nil + } + orOps := right.([]any) + if len(orOps) == 0 { + return left, nil + } + leftCE := left.(Expression) + return NewLogicalExpression(LogicalOperatorOr, leftCE, orOps) +} + +OrOp <- _ ("||" / "|") _ f:And { + debug("matched OR op %s\n", string(c.text)) + fc := f.(Expression) + return fc, nil +} + + + +And <- _ left:LogicalTerm _ right:AndOp* { + debug("matched AND %s\n", string(c.text)) + if right == nil { + return left, nil + } + addOps := right.([]any) + if len(addOps) == 0 { + return left, nil + } + leftCE := left.(Expression) + return NewLogicalExpression(LogicalOperatorAnd, leftCE, addOps) +} + +AndOp <- _ ("&&" / "&") _ f:LogicalTerm { + debug("matched AND op %s\n", string(c.text)) + fc := f.(Expression) + return fc, nil +} diff --git a/testing/pics/grammar/pics.peg b/testing/pics/grammar/pics.peg new file mode 100644 index 00000000..ee8231b1 --- /dev/null +++ b/testing/pics/grammar/pics.peg @@ -0,0 +1,41 @@ +{ +package pics + +import ( + "fmt" + "strconv" +) + +func debug(format string, a ...any) (n int, err error) { + //return + return fmt.Fprintf(os.Stdout, format, a...) +} + +} + + +PICS <- pics:( Logical ) EOF { + return pics, nil +} + + +PICSOp <- not:'!'? pics:PICSIdentifier parens:('(' PICSIdentifier ')')? &PICSSeparator { + debug("matched pics op %s\n", string(c.text)) + p := pics.(string) + pe := &PICSExpression{PICS:p} + if not != nil { + pe.Not = true + } + return pe, nil +} + +PICSIdentifier <- [a-z]i ([a-z0-9]i / '.' / '-' / '_')* { + debug("matched pics %s\n", string(c.text)) + return string(c.text), nil +} + +EOF <- !. + +_ "whitespace" <- [ \t\r\n]* + +PICSSeparator <- _ / &EOF \ No newline at end of file diff --git a/testing/pics/logical.go b/testing/pics/logical.go new file mode 100644 index 00000000..9c79fc4d --- /dev/null +++ b/testing/pics/logical.go @@ -0,0 +1,100 @@ +package pics + +import ( + "fmt" + "strings" +) + +type LogicalOperator uint8 + +const ( + LogicalOperatorNone LogicalOperator = iota + LogicalOperatorAnd + LogicalOperatorOr +) + +func (co LogicalOperator) String() string { + switch co { + case LogicalOperatorAnd: + return "&&" + case LogicalOperatorOr: + return "||" + default: + return "" + } +} + +func (co LogicalOperator) PythonString() string { + switch co { + case LogicalOperatorAnd: + return "and" + case LogicalOperatorOr: + return "or" + default: + return "" + } +} + +type LogicalExpression struct { + Operand LogicalOperator + Left Expression + Right []Expression + Not bool +} + +func NewLogicalExpression(operator LogicalOperator, left Expression, right []any) (*LogicalExpression, error) { + le := &LogicalExpression{Operand: operator, Left: left} + for _, r := range right { + rce, ok := r.(Expression) + if !ok { + return nil, fmt.Errorf("unexpected type in logical expression: %T", r) + } + le.Right = append(le.Right, rce) + } + return le, nil +} + +func (pe LogicalExpression) String() string { + var sb strings.Builder + if pe.Not { + sb.WriteString("!") + } + sb.WriteRune('(') + sb.WriteString(pe.Left.String()) + sb.WriteRune(' ') + for _, re := range pe.Right { + sb.WriteString(pe.Operand.String()) + sb.WriteRune(' ') + sb.WriteString(re.String()) + } + sb.WriteRune(')') + return sb.String() +} + +func (pe LogicalExpression) PythonString() string { + var sb strings.Builder + if pe.Not { + sb.WriteString("not ") + } + sb.WriteString(pe.Left.PythonString()) + for _, re := range pe.Right { + sb.WriteRune(' ') + sb.WriteString(pe.Operand.PythonString()) + sb.WriteRune(' ') + sb.WriteString(re.PythonString()) + } + return sb.String() +} + +func (pe LogicalExpression) PythonBuilder(aliases map[string]string, sb *strings.Builder) { + if pe.Not { + sb.WriteString("not ") + } + pe.Left.PythonBuilder(aliases, sb) + for _, re := range pe.Right { + sb.WriteRune(' ') + sb.WriteString(pe.Operand.PythonString()) + sb.WriteRune(' ') + re.PythonBuilder(aliases, sb) + } +} diff --git a/testing/pics/parser.go b/testing/pics/parser.go new file mode 100644 index 00000000..d239df45 --- /dev/null +++ b/testing/pics/parser.go @@ -0,0 +1,1579 @@ +// Code generated by pigeon; DO NOT EDIT. + +package pics + +import ( + "bytes" + "errors" + "fmt" + "io" + "math" + "os" + "sort" + "strconv" + "strings" + "unicode" + "unicode/utf8" +) + +func debug(format string, a ...any) (n int, err error) { + //return + return fmt.Fprintf(os.Stdout, format, a...) +} + +var g = &grammar{ + rules: []*rule{ + { + name: "PICS", + pos: position{line: 17, col: 1, offset: 171}, + expr: &actionExpr{ + pos: position{line: 17, col: 9, offset: 179}, + run: (*parser).callonPICS1, + expr: &seqExpr{ + pos: position{line: 17, col: 9, offset: 179}, + exprs: []any{ + &labeledExpr{ + pos: position{line: 17, col: 9, offset: 179}, + label: "pics", + expr: &ruleRefExpr{ + pos: position{line: 17, col: 16, offset: 186}, + offset: 7, + }, + }, + &ruleRefExpr{ + pos: position{line: 17, col: 26, offset: 196}, + offset: 3, + }, + }, + }, + }, + }, + { + name: "PICSOp", + pos: position{line: 22, col: 1, offset: 227}, + expr: &actionExpr{ + pos: position{line: 22, col: 11, offset: 237}, + run: (*parser).callonPICSOp1, + expr: &seqExpr{ + pos: position{line: 22, col: 11, offset: 237}, + exprs: []any{ + &labeledExpr{ + pos: position{line: 22, col: 11, offset: 237}, + label: "not", + expr: &zeroOrOneExpr{ + pos: position{line: 22, col: 15, offset: 241}, + expr: &litMatcher{ + pos: position{line: 22, col: 15, offset: 241}, + val: "!", + ignoreCase: false, + want: "\"!\"", + }, + }, + }, + &labeledExpr{ + pos: position{line: 22, col: 20, offset: 246}, + label: "pics", + expr: &ruleRefExpr{ + pos: position{line: 22, col: 25, offset: 251}, + offset: 2, + }, + }, + &labeledExpr{ + pos: position{line: 22, col: 40, offset: 266}, + label: "parens", + expr: &zeroOrOneExpr{ + pos: position{line: 22, col: 47, offset: 273}, + expr: &seqExpr{ + pos: position{line: 22, col: 48, offset: 274}, + exprs: []any{ + &litMatcher{ + pos: position{line: 22, col: 48, offset: 274}, + val: "(", + ignoreCase: false, + want: "\"(\"", + }, + &ruleRefExpr{ + pos: position{line: 22, col: 52, offset: 278}, + offset: 2, + }, + &litMatcher{ + pos: position{line: 22, col: 67, offset: 293}, + val: ")", + ignoreCase: false, + want: "\")\"", + }, + }, + }, + }, + }, + &andExpr{ + pos: position{line: 22, col: 73, offset: 299}, + expr: &ruleRefExpr{ + pos: position{line: 22, col: 74, offset: 300}, + offset: 5, + }, + }, + }, + }, + }, + }, + { + name: "PICSIdentifier", + pos: position{line: 31, col: 1, offset: 447}, + expr: &actionExpr{ + pos: position{line: 31, col: 19, offset: 465}, + run: (*parser).callonPICSIdentifier1, + expr: &seqExpr{ + pos: position{line: 31, col: 19, offset: 465}, + exprs: []any{ + &charClassMatcher{ + pos: position{line: 31, col: 19, offset: 465}, + val: "[a-z]i", + ranges: []rune{'a', 'z'}, + ignoreCase: true, + inverted: false, + }, + &zeroOrMoreExpr{ + pos: position{line: 31, col: 26, offset: 472}, + expr: &choiceExpr{ + pos: position{line: 31, col: 27, offset: 473}, + alternatives: []any{ + &charClassMatcher{ + pos: position{line: 31, col: 27, offset: 473}, + val: "[a-z0-9]i", + ranges: []rune{'a', 'z', '0', '9'}, + ignoreCase: true, + inverted: false, + }, + &litMatcher{ + pos: position{line: 31, col: 39, offset: 485}, + val: ".", + ignoreCase: false, + want: "\".\"", + }, + &litMatcher{ + pos: position{line: 31, col: 45, offset: 491}, + val: "-", + ignoreCase: false, + want: "\"-\"", + }, + &litMatcher{ + pos: position{line: 31, col: 51, offset: 497}, + val: "_", + ignoreCase: false, + want: "\"_\"", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "EOF", + pos: position{line: 35, col: 1, offset: 543}, + expr: ¬Expr{ + pos: position{line: 35, col: 8, offset: 550}, + expr: &anyMatcher{ + line: 35, col: 9, offset: 551, + }, + }, + }, + { + name: "_", + displayName: "\"whitespace\"", + pos: position{line: 37, col: 1, offset: 554}, + expr: &zeroOrMoreExpr{ + pos: position{line: 37, col: 19, offset: 572}, + expr: &charClassMatcher{ + pos: position{line: 37, col: 19, offset: 572}, + val: "[ \\t\\r\\n]", + chars: []rune{' ', '\t', '\r', '\n'}, + ignoreCase: false, + inverted: false, + }, + }, + }, + { + name: "PICSSeparator", + pos: position{line: 39, col: 1, offset: 584}, + expr: &choiceExpr{ + pos: position{line: 39, col: 18, offset: 601}, + alternatives: []any{ + &ruleRefExpr{ + pos: position{line: 39, col: 18, offset: 601}, + offset: 4, + }, + &andExpr{ + pos: position{line: 39, col: 22, offset: 605}, + expr: &ruleRefExpr{ + pos: position{line: 39, col: 23, offset: 606}, + offset: 3, + }, + }, + }, + }, + }, + { + name: "LogicalTerm", + pos: position{line: 42, col: 1, offset: 612}, + expr: &choiceExpr{ + pos: position{line: 42, col: 16, offset: 627}, + alternatives: []any{ + &actionExpr{ + pos: position{line: 42, col: 16, offset: 627}, + run: (*parser).callonLogicalTerm2, + expr: &seqExpr{ + pos: position{line: 42, col: 16, offset: 627}, + exprs: []any{ + &ruleRefExpr{ + pos: position{line: 42, col: 16, offset: 627}, + offset: 4, + }, + &labeledExpr{ + pos: position{line: 42, col: 18, offset: 629}, + label: "not", + expr: &zeroOrOneExpr{ + pos: position{line: 42, col: 22, offset: 633}, + expr: &litMatcher{ + pos: position{line: 42, col: 22, offset: 633}, + val: "!", + ignoreCase: false, + want: "\"!\"", + }, + }, + }, + &litMatcher{ + pos: position{line: 42, col: 27, offset: 638}, + val: "(", + ignoreCase: false, + want: "\"(\"", + }, + &ruleRefExpr{ + pos: position{line: 42, col: 31, offset: 642}, + offset: 4, + }, + &labeledExpr{ + pos: position{line: 42, col: 34, offset: 645}, + label: "eq", + expr: &ruleRefExpr{ + pos: position{line: 42, col: 37, offset: 648}, + offset: 7, + }, + }, + &ruleRefExpr{ + pos: position{line: 42, col: 45, offset: 656}, + offset: 4, + }, + &litMatcher{ + pos: position{line: 42, col: 47, offset: 658}, + val: ")", + ignoreCase: false, + want: "\")\"", + }, + &ruleRefExpr{ + pos: position{line: 42, col: 51, offset: 662}, + offset: 4, + }, + }, + }, + }, + &actionExpr{ + pos: position{line: 50, col: 6, offset: 830}, + run: (*parser).callonLogicalTerm15, + expr: &seqExpr{ + pos: position{line: 50, col: 6, offset: 830}, + exprs: []any{ + &ruleRefExpr{ + pos: position{line: 50, col: 6, offset: 830}, + offset: 4, + }, + &labeledExpr{ + pos: position{line: 50, col: 9, offset: 833}, + label: "eq", + expr: &ruleRefExpr{ + pos: position{line: 50, col: 12, offset: 836}, + offset: 1, + }, + }, + &ruleRefExpr{ + pos: position{line: 50, col: 19, offset: 843}, + offset: 4, + }, + }, + }, + }, + }, + }, + }, + { + name: "Logical", + pos: position{line: 54, col: 1, offset: 874}, + expr: &ruleRefExpr{ + pos: position{line: 54, col: 12, offset: 885}, + offset: 8, + }, + }, + { + name: "Or", + pos: position{line: 56, col: 1, offset: 889}, + expr: &actionExpr{ + pos: position{line: 56, col: 7, offset: 895}, + run: (*parser).callonOr1, + expr: &seqExpr{ + pos: position{line: 56, col: 7, offset: 895}, + exprs: []any{ + &ruleRefExpr{ + pos: position{line: 56, col: 7, offset: 895}, + offset: 4, + }, + &labeledExpr{ + pos: position{line: 56, col: 9, offset: 897}, + label: "left", + expr: &ruleRefExpr{ + pos: position{line: 56, col: 14, offset: 902}, + offset: 10, + }, + }, + &ruleRefExpr{ + pos: position{line: 56, col: 18, offset: 906}, + offset: 4, + }, + &labeledExpr{ + pos: position{line: 56, col: 20, offset: 908}, + label: "right", + expr: &zeroOrMoreExpr{ + pos: position{line: 56, col: 26, offset: 914}, + expr: &ruleRefExpr{ + pos: position{line: 56, col: 26, offset: 914}, + offset: 9, + }, + }, + }, + }, + }, + }, + }, + { + name: "OrOp", + pos: position{line: 68, col: 1, offset: 1163}, + expr: &actionExpr{ + pos: position{line: 68, col: 9, offset: 1171}, + run: (*parser).callonOrOp1, + expr: &seqExpr{ + pos: position{line: 68, col: 9, offset: 1171}, + exprs: []any{ + &ruleRefExpr{ + pos: position{line: 68, col: 9, offset: 1171}, + offset: 4, + }, + &choiceExpr{ + pos: position{line: 68, col: 12, offset: 1174}, + alternatives: []any{ + &litMatcher{ + pos: position{line: 68, col: 12, offset: 1174}, + val: "||", + ignoreCase: false, + want: "\"||\"", + }, + &litMatcher{ + pos: position{line: 68, col: 19, offset: 1181}, + val: "|", + ignoreCase: false, + want: "\"|\"", + }, + }, + }, + &ruleRefExpr{ + pos: position{line: 68, col: 24, offset: 1186}, + offset: 4, + }, + &labeledExpr{ + pos: position{line: 68, col: 26, offset: 1188}, + label: "f", + expr: &ruleRefExpr{ + pos: position{line: 68, col: 28, offset: 1190}, + offset: 10, + }, + }, + }, + }, + }, + }, + { + name: "And", + pos: position{line: 75, col: 1, offset: 1249}, + expr: &actionExpr{ + pos: position{line: 75, col: 8, offset: 1256}, + run: (*parser).callonAnd1, + expr: &seqExpr{ + pos: position{line: 75, col: 8, offset: 1256}, + exprs: []any{ + &ruleRefExpr{ + pos: position{line: 75, col: 8, offset: 1256}, + offset: 4, + }, + &labeledExpr{ + pos: position{line: 75, col: 10, offset: 1258}, + label: "left", + expr: &ruleRefExpr{ + pos: position{line: 75, col: 15, offset: 1263}, + offset: 6, + }, + }, + &ruleRefExpr{ + pos: position{line: 75, col: 27, offset: 1275}, + offset: 4, + }, + &labeledExpr{ + pos: position{line: 75, col: 29, offset: 1277}, + label: "right", + expr: &zeroOrMoreExpr{ + pos: position{line: 75, col: 35, offset: 1283}, + expr: &ruleRefExpr{ + pos: position{line: 75, col: 35, offset: 1283}, + offset: 11, + }, + }, + }, + }, + }, + }, + }, + { + name: "AndOp", + pos: position{line: 87, col: 1, offset: 1538}, + expr: &actionExpr{ + pos: position{line: 87, col: 10, offset: 1547}, + run: (*parser).callonAndOp1, + expr: &seqExpr{ + pos: position{line: 87, col: 10, offset: 1547}, + exprs: []any{ + &ruleRefExpr{ + pos: position{line: 87, col: 10, offset: 1547}, + offset: 4, + }, + &choiceExpr{ + pos: position{line: 87, col: 13, offset: 1550}, + alternatives: []any{ + &litMatcher{ + pos: position{line: 87, col: 13, offset: 1550}, + val: "&&", + ignoreCase: false, + want: "\"&&\"", + }, + &litMatcher{ + pos: position{line: 87, col: 20, offset: 1557}, + val: "&", + ignoreCase: false, + want: "\"&\"", + }, + }, + }, + &ruleRefExpr{ + pos: position{line: 87, col: 25, offset: 1562}, + offset: 4, + }, + &labeledExpr{ + pos: position{line: 87, col: 27, offset: 1564}, + label: "f", + expr: &ruleRefExpr{ + pos: position{line: 87, col: 29, offset: 1566}, + offset: 6, + }, + }, + }, + }, + }, + }, + }, +} + +func (c *current) onPICS1(pics any) (any, error) { + return pics, nil +} + +func (p *parser) callonPICS1() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onPICS1(stack["pics"]) +} + +func (c *current) onPICSOp1(not, pics, parens any) (any, error) { + p := pics.(string) + pe := &PICSExpression{PICS: p} + if not != nil { + pe.Not = true + } + return pe, nil +} + +func (p *parser) callonPICSOp1() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onPICSOp1(stack["not"], stack["pics"], stack["parens"]) +} + +func (c *current) onPICSIdentifier1() (any, error) { + return string(c.text), nil +} + +func (p *parser) callonPICSIdentifier1() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onPICSIdentifier1() +} + +func (c *current) onLogicalTerm2(not, eq any) (any, error) { + if not != nil { + switch eq := eq.(type) { + case *LogicalExpression: + eq.Not = true + } + } + return eq, nil +} + +func (p *parser) callonLogicalTerm2() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onLogicalTerm2(stack["not"], stack["eq"]) +} + +func (c *current) onLogicalTerm15(eq any) (any, error) { + return eq, nil +} + +func (p *parser) callonLogicalTerm15() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onLogicalTerm15(stack["eq"]) +} + +func (c *current) onOr1(left, right any) (any, error) { + if right == nil { + return left, nil + } + orOps := right.([]any) + if len(orOps) == 0 { + return left, nil + } + leftCE := left.(Expression) + return NewLogicalExpression(LogicalOperatorOr, leftCE, orOps) +} + +func (p *parser) callonOr1() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onOr1(stack["left"], stack["right"]) +} + +func (c *current) onOrOp1(f any) (any, error) { + fc := f.(Expression) + return fc, nil +} + +func (p *parser) callonOrOp1() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onOrOp1(stack["f"]) +} + +func (c *current) onAnd1(left, right any) (any, error) { + if right == nil { + return left, nil + } + addOps := right.([]any) + if len(addOps) == 0 { + return left, nil + } + leftCE := left.(Expression) + return NewLogicalExpression(LogicalOperatorAnd, leftCE, addOps) +} + +func (p *parser) callonAnd1() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onAnd1(stack["left"], stack["right"]) +} + +func (c *current) onAndOp1(f any) (any, error) { + fc := f.(Expression) + return fc, nil +} + +func (p *parser) callonAndOp1() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onAndOp1(stack["f"]) +} + +var ( + // errNoRule is returned when the grammar to parse has no rule. + errNoRule = errors.New("grammar has no rule") + + // errInvalidEntrypoint is returned when the specified entrypoint rule + // does not exit. + errInvalidEntrypoint = errors.New("invalid entrypoint") + + // errInvalidEncoding is returned when the source is not properly + // utf8-encoded. + errInvalidEncoding = errors.New("invalid encoding") + + // errMaxExprCnt is used to signal that the maximum number of + // expressions have been parsed. + errMaxExprCnt = errors.New("max number of expressions parsed") +) + +// Option is a function that can set an option on the parser. It returns +// the previous setting as an Option. +type Option func(*parser) Option + +// MaxExpressions creates an Option to stop parsing after the provided +// number of expressions have been parsed, if the value is 0 then the parser will +// parse for as many steps as needed (possibly an infinite number). +// +// The default for maxExprCnt is 0. +func MaxExpressions(maxExprCnt uint64) Option { + return func(p *parser) Option { + oldMaxExprCnt := p.maxExprCnt + p.maxExprCnt = maxExprCnt + return MaxExpressions(oldMaxExprCnt) + } +} + +// Entrypoint creates an Option to set the rule name to use as entrypoint. +// The rule name must have been specified in the -alternate-entrypoints +// if generating the parser with the -optimize-grammar flag, otherwise +// it may have been optimized out. Passing an empty string sets the +// entrypoint to the first rule in the grammar. +// +// The default is to start parsing at the first rule in the grammar. +func Entrypoint(ruleName string) Option { + return func(p *parser) Option { + oldEntrypoint := p.entrypoint + p.entrypoint = ruleName + if ruleName == "" { + p.entrypoint = g.rules[0].name + } + return Entrypoint(oldEntrypoint) + } +} + +// AllowInvalidUTF8 creates an Option to allow invalid UTF-8 bytes. +// Every invalid UTF-8 byte is treated as a utf8.RuneError (U+FFFD) +// by character class matchers and is matched by the any matcher. +// The returned matched value, c.text and c.offset are NOT affected. +// +// The default is false. +func AllowInvalidUTF8(b bool) Option { + return func(p *parser) Option { + old := p.allowInvalidUTF8 + p.allowInvalidUTF8 = b + return AllowInvalidUTF8(old) + } +} + +// Recover creates an Option to set the recover flag to b. When set to +// true, this causes the parser to recover from panics and convert it +// to an error. Setting it to false can be useful while debugging to +// access the full stack trace. +// +// The default is true. +func Recover(b bool) Option { + return func(p *parser) Option { + old := p.recover + p.recover = b + return Recover(old) + } +} + +// GlobalStore creates an Option to set a key to a certain value in +// the globalStore. +func GlobalStore(key string, value any) Option { + return func(p *parser) Option { + old := p.cur.globalStore[key] + p.cur.globalStore[key] = value + return GlobalStore(key, old) + } +} + +// ParseFile parses the file identified by filename. +func ParseFile(filename string, opts ...Option) (i any, err error) { + f, err := os.Open(filename) + if err != nil { + return nil, err + } + defer func() { + if closeErr := f.Close(); closeErr != nil { + err = closeErr + } + }() + return ParseReader(filename, f, opts...) +} + +// ParseReader parses the data from r using filename as information in the +// error messages. +func ParseReader(filename string, r io.Reader, opts ...Option) (any, error) { + b, err := io.ReadAll(r) + if err != nil { + return nil, err + } + + return Parse(filename, b, opts...) +} + +// Parse parses the data from b using filename as information in the +// error messages. +func Parse(filename string, b []byte, opts ...Option) (any, error) { + return newParser(filename, b, opts...).parse(g) +} + +// position records a position in the text. +type position struct { + line, col, offset int +} + +func (p position) String() string { + return strconv.Itoa(p.line) + ":" + strconv.Itoa(p.col) + " [" + strconv.Itoa(p.offset) + "]" +} + +// savepoint stores all state required to go back to this point in the +// parser. +type savepoint struct { + position + rn rune + w int +} + +type current struct { + pos position // start position of the match + text []byte // raw text of the match + + // globalStore is a general store for the user to store arbitrary key-value + // pairs that they need to manage and that they do not want tied to the + // backtracking of the parser. This is only modified by the user and never + // rolled back by the parser. It is always up to the user to keep this in a + // consistent state. + globalStore storeDict + parser *parser +} + +type storeDict map[string]any + +// the AST types... + +type grammar struct { + pos position + rules []*rule +} + +type rule struct { + pos position + name string + displayName string + expr any +} + +type choiceExpr struct { + pos position + alternatives []any +} + +type actionExpr struct { + pos position + expr any + run func(*parser) (any, error) +} + +type recoveryExpr struct { + pos position + expr any + recoverExpr any + failureLabel []string +} + +type seqExpr struct { + pos position + exprs []any +} + +type throwExpr struct { + pos position + label string +} + +type labeledExpr struct { + pos position + label string + expr any +} + +type expr struct { + pos position + expr any +} + +type ( + andExpr expr + notExpr expr + zeroOrOneExpr expr + zeroOrMoreExpr expr + oneOrMoreExpr expr +) + +type ruleRefExpr struct { + pos position + offset int +} + +type andCodeExpr struct { + pos position + run func(*parser) (bool, error) +} + +type notCodeExpr struct { + pos position + run func(*parser) (bool, error) +} + +type litMatcher struct { + pos position + val string + ignoreCase bool + want string +} + +type charClassMatcher struct { + pos position + val string + //basicLatinChars [128]bool + chars []rune + ranges []rune + classes []*unicode.RangeTable + ignoreCase bool + inverted bool +} + +type anyMatcher position + +// errList cumulates the errors found by the parser. +type errList []error + +func (e *errList) add(err error) { + *e = append(*e, err) +} + +func (e errList) err() error { + if len(e) == 0 { + return nil + } + e.dedupe() + return e +} + +func (e *errList) dedupe() { + var cleaned []error + set := make(map[string]bool) + for _, err := range *e { + if msg := err.Error(); !set[msg] { + set[msg] = true + cleaned = append(cleaned, err) + } + } + *e = cleaned +} + +func (e errList) Error() string { + switch len(e) { + case 0: + return "" + case 1: + return e[0].Error() + default: + var buf bytes.Buffer + + for i, err := range e { + if i > 0 { + buf.WriteRune('\n') + } + buf.WriteString(err.Error()) + } + return buf.String() + } +} + +// parserError wraps an error with a prefix indicating the rule in which +// the error occurred. The original error is stored in the Inner field. +type parserError struct { + Inner error + pos position + prefix string + expected []string +} + +// Error returns the error message. +func (p *parserError) Error() string { + return p.prefix + ": " + p.Inner.Error() +} + +// newParser creates a parser with the specified input source and options. +func newParser(filename string, b []byte, opts ...Option) *parser { + stats := Stats{ + ChoiceAltCnt: make(map[string]map[string]int), + } + + p := &parser{ + filename: filename, + errs: new(errList), + data: b, + pt: savepoint{position: position{line: 1}}, + recover: true, + cur: current{ + globalStore: make(storeDict), + }, + maxFailPos: position{col: 1, line: 1}, + maxFailExpected: make([]string, 0, 20), + Stats: &stats, + // start rule is rule [0] unless an alternate entrypoint is specified + entrypoint: g.rules[0].name, + } + p.setOptions(opts) + p.cur.parser = p + + if p.maxExprCnt == 0 { + p.maxExprCnt = math.MaxUint64 + } + + return p +} + +// setOptions applies the options to the parser. +func (p *parser) setOptions(opts []Option) { + for _, opt := range opts { + opt(p) + } +} + +type resultTuple struct { + v any + b bool + end savepoint +} + +const choiceNoMatch = -1 + +// Stats stores some statistics, gathered during parsing +type Stats struct { + // ExprCnt counts the number of expressions processed during parsing + // This value is compared to the maximum number of expressions allowed + // (set by the MaxExpressions option). + ExprCnt uint64 + + // ChoiceAltCnt is used to count for each ordered choice expression, + // which alternative is used how may times. + // These numbers allow to optimize the order of the ordered choice expression + // to increase the performance of the parser + // + // The outer key of ChoiceAltCnt is composed of the name of the rule as well + // as the line and the column of the ordered choice. + // The inner key of ChoiceAltCnt is the number (one-based) of the matching alternative. + // For each alternative the number of matches are counted. If an ordered choice does not + // match, a special counter is incremented. The name of this counter is set with + // the parser option Statistics. + // For an alternative to be included in ChoiceAltCnt, it has to match at least once. + ChoiceAltCnt map[string]map[string]int +} + +type parser struct { + filename string + pt savepoint + cur current + + data []byte + errs *errList + + depth int + recover bool + + // rules table, maps the rule offset to the rule node + rules []*rule + // variables stack, map of label to value + vstack []map[string]any + // rule stack, allows identification of the current rule in errors + rstack []*rule + + // parse fail + maxFailPos position + maxFailExpected []string + maxFailInvertExpected bool + + // max number of expressions to be parsed + maxExprCnt uint64 + // entrypoint for the parser + entrypoint string + + allowInvalidUTF8 bool + + *Stats + + choiceNoMatch string + // recovery expression stack, keeps track of the currently available recovery expression, these are traversed in reverse + recoveryStack []map[string]any + offset position +} + +// push a variable set on the vstack. +func (p *parser) pushV() { + if cap(p.vstack) == len(p.vstack) { + // create new empty slot in the stack + p.vstack = append(p.vstack, nil) + } else { + // slice to 1 more + p.vstack = p.vstack[:len(p.vstack)+1] + } + + // get the last args set + m := p.vstack[len(p.vstack)-1] + if m != nil && len(m) == 0 { + // empty map, all good + return + } + + m = make(map[string]any) + p.vstack[len(p.vstack)-1] = m +} + +// pop a variable set from the vstack. +func (p *parser) popV() { + // if the map is not empty, clear it + m := p.vstack[len(p.vstack)-1] + if len(m) > 0 { + // GC that map + p.vstack[len(p.vstack)-1] = nil + } + p.vstack = p.vstack[:len(p.vstack)-1] +} + +// push a recovery expression with its labels to the recoveryStack +func (p *parser) pushRecovery(labels []string, expr any) { + if cap(p.recoveryStack) == len(p.recoveryStack) { + // create new empty slot in the stack + p.recoveryStack = append(p.recoveryStack, nil) + } else { + // slice to 1 more + p.recoveryStack = p.recoveryStack[:len(p.recoveryStack)+1] + } + + m := make(map[string]any, len(labels)) + for _, fl := range labels { + m[fl] = expr + } + p.recoveryStack[len(p.recoveryStack)-1] = m +} + +// pop a recovery expression from the recoveryStack +func (p *parser) popRecovery() { + // GC that map + p.recoveryStack[len(p.recoveryStack)-1] = nil + + p.recoveryStack = p.recoveryStack[:len(p.recoveryStack)-1] +} + +func (p *parser) addErr(err error) { + p.addErrAt(err, p.pt.position, []string{}) +} + +func (p *parser) addErrAt(err error, pos position, expected []string) { + var buf bytes.Buffer + if p.filename != "" { + buf.WriteString(p.filename) + } + if buf.Len() > 0 { + buf.WriteString(":") + } + buf.WriteString(fmt.Sprintf("%d:%d (%d)", pos.line, pos.col, pos.offset)) + if len(p.rstack) > 0 { + if buf.Len() > 0 { + buf.WriteString(": ") + } + rule := p.rstack[len(p.rstack)-1] + if rule.displayName != "" { + buf.WriteString("rule " + rule.displayName) + } else { + buf.WriteString("rule " + rule.name) + } + } + pe := &parserError{Inner: err, pos: pos, prefix: buf.String(), expected: expected} + p.errs.add(pe) +} + +func (p *parser) failAt(fail bool, pos position, want string) { + // process fail if parsing fails and not inverted or parsing succeeds and invert is set + if fail == p.maxFailInvertExpected { + if pos.offset < p.maxFailPos.offset { + return + } + + if pos.offset > p.maxFailPos.offset { + p.maxFailPos = pos + p.maxFailExpected = p.maxFailExpected[:0] + } + + if p.maxFailInvertExpected { + want = "!" + want + } + p.maxFailExpected = append(p.maxFailExpected, want) + } +} + +// read advances the parser to the next rune. +func (p *parser) read() { + p.pt.offset += p.pt.w + rn, n := utf8.DecodeRune(p.data[p.pt.offset:]) + p.pt.rn = rn + p.pt.w = n + p.pt.col++ + if rn == '\n' { + p.pt.line++ + p.pt.col = 0 + } + + if rn == utf8.RuneError && n == 1 { // see utf8.DecodeRune + if !p.allowInvalidUTF8 { + p.addErr(errInvalidEncoding) + } + } +} + +// restore parser position to the savepoint pt. +func (p *parser) restore(pt savepoint) { + if pt.offset == p.pt.offset { + return + } + p.pt = pt +} + +// get the slice of bytes from the savepoint start to the current position. +func (p *parser) sliceFrom(start savepoint) []byte { + return p.data[start.position.offset:p.pt.position.offset] +} + +func (p *parser) parse(g *grammar) (val any, err error) { + if len(g.rules) == 0 { + p.addErr(errNoRule) + return nil, p.errs.err() + } + + // TODO : not super critical but this could be generated + p.rules = g.rules + + if p.recover { + // panic can be used in action code to stop parsing immediately + // and return the panic as an error. + defer func() { + if e := recover(); e != nil { + val = nil + switch e := e.(type) { + case error: + p.addErr(e) + default: + p.addErr(fmt.Errorf("%v", e)) + } + err = p.errs.err() + } + }() + } + + var startRule *rule + for _, r := range p.rules { + if r.name == p.entrypoint { + startRule = r + break + } + } + if startRule == nil { + p.addErr(errInvalidEntrypoint) + return nil, p.errs.err() + } + + p.read() // advance to first rune + var ok bool + val, ok = p.parseRuleWrap(startRule) + if !ok { + if len(*p.errs) == 0 { + // If parsing fails, but no errors have been recorded, the expected values + // for the farthest parser position are returned as error. + maxFailExpectedMap := make(map[string]struct{}, len(p.maxFailExpected)) + for _, v := range p.maxFailExpected { + maxFailExpectedMap[v] = struct{}{} + } + expected := make([]string, 0, len(maxFailExpectedMap)) + eof := false + if _, ok := maxFailExpectedMap["!."]; ok { + delete(maxFailExpectedMap, "!.") + eof = true + } + for k := range maxFailExpectedMap { + expected = append(expected, k) + } + sort.Strings(expected) + if eof { + expected = append(expected, "EOF") + } + p.addErrAt(errors.New("no match found, expected: "+listJoin(expected, ", ", "or")), p.maxFailPos, expected) + } + + return nil, p.errs.err() + } + return val, p.errs.err() +} + +func listJoin(list []string, sep string, lastSep string) string { + switch len(list) { + case 0: + return "" + case 1: + return list[0] + default: + return strings.Join(list[:len(list)-1], sep) + " " + lastSep + " " + list[len(list)-1] + } +} + +func (p *parser) parseRuleWrap(rule *rule) (any, bool) { + var ( + val any + ok bool + ) + + val, ok = p.parseRule(rule) + + return val, ok +} + +func (p *parser) parseRule(rule *rule) (any, bool) { + p.rstack = append(p.rstack, rule) + p.pushV() + val, ok := p.parseExprWrap(rule.expr) + p.popV() + p.rstack = p.rstack[:len(p.rstack)-1] + return val, ok +} + +func (p *parser) parseExprWrap(expr any) (any, bool) { + val, ok := p.parseExpr(expr) + + return val, ok +} + +func (p *parser) parseExpr(expr any) (any, bool) { + p.ExprCnt++ + if p.ExprCnt > p.maxExprCnt { + panic(errMaxExprCnt) + } + + var val any + var ok bool + switch expr := expr.(type) { + case *actionExpr: + val, ok = p.parseActionExpr(expr) + case *andCodeExpr: + val, ok = p.parseAndCodeExpr(expr) + case *andExpr: + val, ok = p.parseAndExpr(expr) + case *anyMatcher: + val, ok = p.parseAnyMatcher(expr) + case *charClassMatcher: + val, ok = p.parseCharClassMatcher(expr) + case *choiceExpr: + val, ok = p.parseChoiceExpr(expr) + case *labeledExpr: + val, ok = p.parseLabeledExpr(expr) + case *litMatcher: + val, ok = p.parseLitMatcher(expr) + case *notCodeExpr: + val, ok = p.parseNotCodeExpr(expr) + case *notExpr: + val, ok = p.parseNotExpr(expr) + case *oneOrMoreExpr: + val, ok = p.parseOneOrMoreExpr(expr) + case *recoveryExpr: + val, ok = p.parseRecoveryExpr(expr) + case *ruleRefExpr: + val, ok = p.parseRuleRefExpr(expr) + case *seqExpr: + val, ok = p.parseSeqExpr(expr) + case *throwExpr: + val, ok = p.parseThrowExpr(expr) + case *zeroOrMoreExpr: + val, ok = p.parseZeroOrMoreExpr(expr) + case *zeroOrOneExpr: + val, ok = p.parseZeroOrOneExpr(expr) + default: + panic(fmt.Sprintf("unknown expression type %T", expr)) + } + return val, ok +} + +func (p *parser) parseActionExpr(act *actionExpr) (any, bool) { + start := p.pt + val, ok := p.parseExprWrap(act.expr) + if ok { + p.cur.pos = start.position + p.cur.text = p.sliceFrom(start) + actVal, err := act.run(p) + if err != nil { + p.addErrAt(err, start.position, []string{}) + } + + val = actVal + } + return val, ok +} + +func (p *parser) parseAndCodeExpr(and *andCodeExpr) (any, bool) { + + ok, err := and.run(p) + if err != nil { + p.addErr(err) + } + + return nil, ok +} + +func (p *parser) parseAndExpr(and *andExpr) (any, bool) { + pt := p.pt + p.pushV() + _, ok := p.parseExprWrap(and.expr) + p.popV() + p.restore(pt) + + return nil, ok +} + +func (p *parser) parseAnyMatcher(any *anyMatcher) (any, bool) { + if p.pt.rn == utf8.RuneError && p.pt.w == 0 { + // EOF - see utf8.DecodeRune + p.failAt(false, p.pt.position, ".") + return nil, false + } + start := p.pt + p.read() + p.failAt(true, start.position, ".") + return p.sliceFrom(start), true +} + +func (p *parser) parseCharClassMatcher(chr *charClassMatcher) (any, bool) { + cur := p.pt.rn + start := p.pt + + // can't match EOF + if cur == utf8.RuneError && p.pt.w == 0 { // see utf8.DecodeRune + p.failAt(false, start.position, chr.val) + return nil, false + } + + if chr.ignoreCase { + cur = unicode.ToLower(cur) + } + + // try to match in the list of available chars + for _, rn := range chr.chars { + if rn == cur { + if chr.inverted { + p.failAt(false, start.position, chr.val) + return nil, false + } + p.read() + p.failAt(true, start.position, chr.val) + return p.sliceFrom(start), true + } + } + + // try to match in the list of ranges + for i := 0; i < len(chr.ranges); i += 2 { + if cur >= chr.ranges[i] && cur <= chr.ranges[i+1] { + if chr.inverted { + p.failAt(false, start.position, chr.val) + return nil, false + } + p.read() + p.failAt(true, start.position, chr.val) + return p.sliceFrom(start), true + } + } + + // try to match in the list of Unicode classes + for _, cl := range chr.classes { + if unicode.Is(cl, cur) { + if chr.inverted { + p.failAt(false, start.position, chr.val) + return nil, false + } + p.read() + p.failAt(true, start.position, chr.val) + return p.sliceFrom(start), true + } + } + + if chr.inverted { + p.read() + p.failAt(true, start.position, chr.val) + return p.sliceFrom(start), true + } + p.failAt(false, start.position, chr.val) + return nil, false +} + +func (p *parser) parseChoiceExpr(ch *choiceExpr) (any, bool) { + + for altI, alt := range ch.alternatives { + // dummy assignment to prevent compile error if optimized + _ = altI + + p.pushV() + val, ok := p.parseExprWrap(alt) + p.popV() + if ok { + return val, ok + } + } + return nil, false +} + +func (p *parser) parseLabeledExpr(lab *labeledExpr) (any, bool) { + p.pushV() + val, ok := p.parseExprWrap(lab.expr) + p.popV() + if ok && lab.label != "" { + m := p.vstack[len(p.vstack)-1] + m[lab.label] = val + } + return val, ok +} + +func (p *parser) parseLitMatcher(lit *litMatcher) (any, bool) { + start := p.pt + for _, want := range lit.val { + cur := p.pt.rn + if lit.ignoreCase { + cur = unicode.ToLower(cur) + } + if cur != want { + p.failAt(false, start.position, lit.want) + p.restore(start) + return nil, false + } + p.read() + } + p.failAt(true, start.position, lit.want) + return p.sliceFrom(start), true +} + +func (p *parser) parseNotCodeExpr(not *notCodeExpr) (any, bool) { + ok, err := not.run(p) + if err != nil { + p.addErr(err) + } + + return nil, !ok +} + +func (p *parser) parseNotExpr(not *notExpr) (any, bool) { + pt := p.pt + p.pushV() + p.maxFailInvertExpected = !p.maxFailInvertExpected + _, ok := p.parseExprWrap(not.expr) + p.maxFailInvertExpected = !p.maxFailInvertExpected + p.popV() + p.restore(pt) + + return nil, !ok +} + +func (p *parser) parseOneOrMoreExpr(expr *oneOrMoreExpr) (any, bool) { + var vals []any + + for { + p.pushV() + val, ok := p.parseExprWrap(expr.expr) + p.popV() + if !ok { + if len(vals) == 0 { + // did not match once, no match + return nil, false + } + return vals, true + } + vals = append(vals, val) + } +} + +func (p *parser) parseRecoveryExpr(recover *recoveryExpr) (any, bool) { + + p.pushRecovery(recover.failureLabel, recover.recoverExpr) + val, ok := p.parseExprWrap(recover.expr) + p.popRecovery() + + return val, ok +} + +func (p *parser) parseRuleRefExpr(ref *ruleRefExpr) (any, bool) { + if ref.offset > len(p.rules)-1 { + panic(fmt.Sprintf("%s: invalid rule: out of range", ref.pos)) + } + + rule := p.rules[ref.offset] + return p.parseRuleWrap(rule) +} + +func (p *parser) parseSeqExpr(seq *seqExpr) (any, bool) { + var vals []any + + pt := p.pt + for _, expr := range seq.exprs { + val, ok := p.parseExprWrap(expr) + if !ok { + p.restore(pt) + return nil, false + } + vals = append(vals, val) + } + return vals, true +} + +func (p *parser) parseThrowExpr(expr *throwExpr) (any, bool) { + + for i := len(p.recoveryStack) - 1; i >= 0; i-- { + if recoverExpr, ok := p.recoveryStack[i][expr.label]; ok { + if val, ok := p.parseExprWrap(recoverExpr); ok { + return val, ok + } + } + } + + return nil, false +} + +func (p *parser) parseZeroOrMoreExpr(expr *zeroOrMoreExpr) (any, bool) { + var vals []any + + for { + p.pushV() + val, ok := p.parseExprWrap(expr.expr) + p.popV() + if !ok { + return vals, true + } + vals = append(vals, val) + } +} + +func (p *parser) parseZeroOrOneExpr(expr *zeroOrOneExpr) (any, bool) { + p.pushV() + val, _ := p.parseExprWrap(expr.expr) + p.popV() + // whether it matched or not, consider it a match + return val, true +} diff --git a/testing/pics/pics.go b/testing/pics/pics.go new file mode 100644 index 00000000..95f253f0 --- /dev/null +++ b/testing/pics/pics.go @@ -0,0 +1,65 @@ +package pics + +import ( + "fmt" + "strconv" + "strings" +) + +type PICSExpression struct { + PICS string `json:"id"` + Not bool `json:"not,omitempty"` +} + +func (pe PICSExpression) String() string { + if pe.Not { + return "!" + pe.PICS + } + return pe.PICS +} + +func (pe PICSExpression) PythonString() string { + var sb strings.Builder + if pe.Not { + sb.WriteString("not ") + } + sb.WriteString(`self.check_pics(`) + sb.WriteString(strconv.Quote(pe.PICS)) + sb.WriteString(")") + return sb.String() +} + +func (pe PICSExpression) PythonBuilder(aliases map[string]string, sb *strings.Builder) { + alias, ok := aliases[pe.PICS] + if pe.Not { + sb.WriteString("not ") + } + if ok && alias != "" { + sb.WriteString(alias) + return + } + sb.WriteString(`self.check_pics(`) + sb.WriteString(strconv.Quote(pe.PICS)) + sb.WriteString(")") +} + +func ParseString(pics string) (Expression, error) { + pics = strings.TrimSpace(pics) + if pics == "" { + return nil, nil + } + c, err := Parse("", []byte(pics)) + if err != nil { + err = fmt.Errorf("error parsing PICS \"%s\": %w", pics, err) + return nil, err + } + switch c := c.(type) { + case Expression: + return c, nil + case []any: + for _, cs := range c { + fmt.Printf("type: %T\n", cs) + } + } + return c.(Expression), nil +} diff --git a/testplan/attributes.go b/testplan/attributes.go index b07b62ae..04345f07 100644 --- a/testplan/attributes.go +++ b/testplan/attributes.go @@ -1,206 +1 @@ package testplan - -import ( - "fmt" - "strconv" - "strings" - "unicode" - "unicode/utf8" - - "github.com/iancoleman/strcase" - "github.com/project-chip/alchemy/matter" - "github.com/project-chip/alchemy/matter/constraint" - "github.com/project-chip/alchemy/matter/spec" - "github.com/project-chip/alchemy/matter/types" -) - -func renderAttributes(doc *spec.Doc, cut *clusterUnderTest, b *strings.Builder) { - - b.WriteString("==== Attributes\n\n\n") - - if len(cut.attributes) == 0 { - return - } - names := make([]string, 0, len(cut.attributes)) - var longest int - for _, a := range cut.attributes { - name := entityIdentifier(a) - if len(name) > longest { - longest = len(name) - } - names = append(names, name) - } - for i, name := range names { - b.WriteString(":") - b.WriteString(fmt.Sprintf("%-*s", longest, name)) - b.WriteString(" : ") - b.WriteString(cut.attributes[i].Name) - b.WriteRune('\n') - } - b.WriteString("\n\n") - for i, name := range names { - b.WriteString(fmt.Sprintf(":PICS_S%-*s : {PICS_S}.A%04X({%s})\n", longest, name, cut.attributes[i].ID.Value(), name)) - } - b.WriteString("\n\n|===\n") - b.WriteString("| *Variable* | *Description* | *Mandatory/Optional* | *Notes/Additional Constraints*\n") - for i, a := range cut.attributes { - name := names[i] - b.WriteString(fmt.Sprintf("| {PICS_S%s} | {devimp} the _{%s}_ attribute?| ", name, name)) - if len(a.Conformance) > 0 { - b.WriteString("{PICS_S}: ") - renderPicsConformance(b, doc, cut.cluster, a.Conformance) - } - b.WriteString(" |\n") - } - b.WriteString("|===\n\n") - -} - -var attributesTestHeader = ` -// ################# TEST CASE TEMPLATE: START ################# -==== [TC-{picsCode}-2.1] Attributes with Server as DUT -===== Category -Functional conformance - -===== Purpose -This test case verifies the non-global attributes of the {clustername} cluster server. - -===== PICS - -* {PICS_S} - -===== Required Devices -:reqDevices: reqDevices_C_TH_and_S_DUT -include::../common/required_devices.adoc[] - -===== Device Topology - -TH and DUT are on the same fabric. - -===== Test Setup - -{comDutTH}. - -` - -func renderAttributesTest(cluster *matter.Cluster, b *strings.Builder) { - b.WriteString(attributesTestHeader) - - b.WriteString("===== Test Procedure\n") - b.WriteString("[cols=\"5%,5%,10%,40%,40%\"]\n") - b.WriteString("|===\n") - b.WriteString("| **#** | *Ref* | *PICS* | *Test Step* | *Expected Outcome* \n") - b.WriteString("| 1 | | | {comDutTH}. |\n") - - for i, a := range cluster.Attributes { - name := entityIdentifier(a) - - var reply string - var valrange string - - var dt = a.Type - if dt != nil { - - if a.Type.IsArray() { - dt = dt.EntryType - reply = fmt.Sprintf(" - {DUTreply} a list of %s entries", typeString(cluster, dt)) - if a.Constraint != nil { - switch c := a.Constraint.(type) { - case *constraint.AllConstraint: - case *constraint.ExactConstraint: - case *constraint.RangeConstraint: - valrange = fmt.Sprintf("\n - Verify that the list has between %s and %s entries", limitString(cluster, c.Minimum), limitString(cluster, c.Maximum)) - case *constraint.MinConstraint: - valrange = fmt.Sprintf("\n - Verify that the list has %s or more entries", limitString(cluster, c.Minimum)) - case *constraint.MaxConstraint: - valrange = fmt.Sprintf("\n - Verify that the list has no more than %s entries", limitString(cluster, c.Maximum)) - } - } - } else { - reply = " {DUTreply} " - if a.Quality.Has(matter.QualityNullable) { - reply += "either null or " - } - tn := typeString(cluster, dt) - firstLetter, _ := utf8.DecodeRuneInString(tn) - switch unicode.ToLower(firstLetter) { - case 'a', 'e', 'i', 'o', 'u': // Not perfect, but not importing a dictionary for this - reply += "an " - default: - reply += "a " - } - reply += fmt.Sprintf("%s value.", tn) - if a.Constraint != nil { - switch c := a.Constraint.(type) { - case *constraint.AllConstraint: - case *constraint.ExactConstraint: - case *constraint.RangeConstraint: - valrange = fmt.Sprintf(" {valrange} %s", c.ASCIIDocString(a.Type)) - } - } - } - - } - - b.WriteString(fmt.Sprintf("| %d | {REF_%s_S%s} | {PICS_S%s} | {THread} _{%s}_ attribute. |", i+2, cluster.PICS, name, name, name)) - if len(reply) > 0 { - b.WriteString(reply) - } - if len(valrange) > 0 { - b.WriteString(valrange) - } - b.WriteString("\n") - } - b.WriteString("|===\n\n") - b.WriteString("===== Notes/Testing Considerations\n\n\n") - b.WriteString("// ################# TEST CASE TEMPLATE: END #################\n") -} - -func typeString(cluster *matter.Cluster, dt *types.DataType) string { - switch dt.BaseType { - case types.BaseDataTypeCustom: - entity, ok := cluster.Identifier(dt.Name) - if !ok { - return dt.Name - } - switch entity := entity.(type) { - case *matter.Enum: - return entity.Type.Name - case *matter.Bitmap: - return entity.Type.Name - } - return dt.Name - case types.BaseDataTypeVoltage, types.BaseDataTypePower, types.BaseDataTypeEnergy, types.BaseDataTypeAmperage: - return "int64" - default: - return dt.Name - } -} - -func limitString(cluster *matter.Cluster, limit constraint.Limit) string { - switch limit := limit.(type) { - case *constraint.BooleanLimit: - return strconv.FormatBool(limit.Value) - case *constraint.ExpLimit: - return fmt.Sprintf("%d^%d^", limit.Value, limit.Exp) - case *constraint.HexLimit: - return fmt.Sprintf("0x%X", limit.Value) - case *constraint.IntLimit: - return strconv.FormatInt(limit.Value, 10) - case *constraint.TemperatureLimit: - return fmt.Sprintf("%s°C", limit.Value.String()) - case *constraint.ReferenceLimit: - ref, ok := cluster.Identifier(limit.Value) - if !ok { - return fmt.Sprintf("ERR: unknown reference %s", limit.Value) - } - switch ref := ref.(type) { - case *matter.Field: - return fmt.Sprintf("{A_%s}", strcase.ToScreamingSnake(ref.Name)) - default: - return fmt.Sprintf("ERR: unknown reference type %T (%s)", ref, limit.Value) - } - default: - return "unknown limit" - } -} diff --git a/testplan/cluster.go b/testplan/cluster.go index b971a907..ddc9ee1b 100644 --- a/testplan/cluster.go +++ b/testplan/cluster.go @@ -1,8 +1,6 @@ package testplan import ( - "strings" - "github.com/project-chip/alchemy/matter" "github.com/project-chip/alchemy/matter/conformance" "github.com/project-chip/alchemy/matter/spec" @@ -10,36 +8,15 @@ import ( type clusterUnderTest struct { cluster *matter.Cluster - features []*matter.Feature + features []*feature attributes []*matter.Field commandsAccepted []*matter.Command commandsGenerated []*matter.Command events []*matter.Event } -func renderClusterTestPlan(doc *spec.Doc, cluster *matter.Cluster) (output string, err error) { - cut := filterCluster(doc, cluster) - var out strings.Builder - err = renderHeader(cluster, &out) - if err != nil { - return - } - err = renderServer(doc, cut, &out) - if err != nil { - return - } - out.WriteString(testCases) - err = renderGlobalAttributesTestCase(doc, cut, &out) - if err != nil { - return - } - renderAttributesTest(cluster, &out) - output = out.String() - return -} - -func filterCluster(doc *spec.Doc, cluster *matter.Cluster) *clusterUnderTest { - cut := &clusterUnderTest{ +func filterCluster(doc *spec.Doc, cluster *matter.Cluster) (cut *clusterUnderTest, err error) { + cut = &clusterUnderTest{ cluster: cluster, attributes: make([]*matter.Field, 0, len(cluster.Attributes)), } @@ -50,7 +27,17 @@ func filterCluster(doc *spec.Doc, cluster *matter.Cluster) *clusterUnderTest { continue } - f := bit.(*matter.Feature) + feat := bit.(*matter.Feature) + f := &feature{Code: feat.Code, Summary: feat.Summary(), Conformance: feat.Conformance()} + f.From, f.To, err = feat.Bits() + if err != nil { + return + } + if f.From <= f.To { + for i := f.From; i <= f.To; i++ { + f.Bits = append(f.Bits, i) + } + } cut.features = append(cut.features, f) } } @@ -93,21 +80,9 @@ func filterCluster(doc *spec.Doc, cluster *matter.Cluster) *clusterUnderTest { } cut.events = append(cut.events, event) } - return cut + return } func checkConformance(c conformance.Set, store conformance.IdentifierStore) bool { return !(conformance.IsZigbee(store, c) || conformance.IsDisallowed(c) || conformance.IsDeprecated(c)) } - -var testCases = `== Test Case List - -|=== -| *TC UUID* | *Test Case Name* -| TC-{picsCode}-1.1 | Global Attributes with {DUT_Server} -| TC-{picsCode}-2.1 | Attributes with Server as DUT -| TC-{picsCode}-2.2 | Primary Functionality with Server as DUT -|=== - - -` diff --git a/testplan/commands.go b/testplan/commands.go deleted file mode 100644 index f79f306c..00000000 --- a/testplan/commands.go +++ /dev/null @@ -1,93 +0,0 @@ -package testplan - -import ( - "fmt" - "strings" - - "github.com/project-chip/alchemy/matter/spec" -) - -func renderCommands(doc *spec.Doc, cut *clusterUnderTest, b *strings.Builder) { - renderCommandsAccepted(doc, cut, b) - renderCommandsGenerated(doc, cut, b) -} - -func renderCommandsAccepted(doc *spec.Doc, cut *clusterUnderTest, b *strings.Builder) { - if len(cut.commandsAccepted) == 0 { - return - } - - names := make([]string, 0, len(cut.commandsAccepted)) - var longest int - for _, a := range cut.commandsAccepted { - name := entityIdentifier(a) - if len(name) > longest { - longest = len(name) - } - names = append(names, name) - } - b.WriteString("==== Commands received\n\n\n") - for i, name := range names { - b.WriteString(":") - b.WriteString(fmt.Sprintf("%-*s", longest, name)) - b.WriteString(" : ") - b.WriteString(cut.commandsAccepted[i].Name) - b.WriteRune('\n') - } - b.WriteString("\n\n") - for i, name := range names { - b.WriteString(fmt.Sprintf(":PICS_S%-*s : {PICS_S}.C%02X.Rsp({%s})\n", longest, name, cut.commandsAccepted[i].ID.Value(), name)) - } - b.WriteString("\n\n|===\n") - b.WriteString("| *Variable* | *Description* | *Mandatory/Optional* | *Notes/Additional Constraints*\n") - for i, a := range cut.commandsAccepted { - name := names[i] - b.WriteString(fmt.Sprintf("| {PICS_S%s} | {devimp} the _{%s}_ command?| ", name, name)) - if len(a.Conformance) > 0 { - b.WriteString("{PICS_S}: ") - renderPicsConformance(b, doc, cut.cluster, a.Conformance) - } - b.WriteString(" |\n") - } - b.WriteString("|===\n\n") -} - -func renderCommandsGenerated(doc *spec.Doc, cut *clusterUnderTest, b *strings.Builder) { - if len(cut.commandsGenerated) == 0 { - return - } - - names := make([]string, 0, len(cut.commandsGenerated)) - var longest int - for _, a := range cut.commandsGenerated { - name := entityIdentifier(a) - if len(name) > longest { - longest = len(name) - } - names = append(names, name) - } - b.WriteString("==== Commands generated\n\n\n") - for i, name := range names { - b.WriteString(":") - b.WriteString(fmt.Sprintf("%-*s", longest, name)) - b.WriteString(" : ") - b.WriteString(cut.commandsGenerated[i].Name) - b.WriteRune('\n') - } - b.WriteString("\n\n") - for i, name := range names { - b.WriteString(fmt.Sprintf(":PICS_S%-*s : {PICS_S}.C%02X.Tx({%s})\n", longest, name, cut.commandsGenerated[i].ID.Value(), name)) - } - b.WriteString("\n\n|===\n") - b.WriteString("| *Variable* | *Description* | *Mandatory/Optional* | *Notes/Additional Constraints*\n") - for i, a := range cut.commandsGenerated { - name := names[i] - b.WriteString(fmt.Sprintf("| {PICS_S%s} | {devimp} sending the _{%s}_ command?| ", name, name)) - if len(a.Conformance) > 0 { - b.WriteString("{PICS_S}: ") - renderPicsConformance(b, doc, cut.cluster, a.Conformance) - } - b.WriteString(" |\n") - } - b.WriteString("|===\n\n") -} diff --git a/testplan/conformance.go b/testplan/conformance.go index f341d81b..a63ca244 100644 --- a/testplan/conformance.go +++ b/testplan/conformance.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/mailgun/raymond/v2" "github.com/project-chip/alchemy/matter" "github.com/project-chip/alchemy/matter/conformance" "github.com/project-chip/alchemy/matter/spec" @@ -27,38 +28,71 @@ func renderFeatureConformance(b *strings.Builder, doc *spec.Doc, cluster *matter renderConformance(cs, b, doc, cluster, entityVariable) } -func renderConformance(cs conformance.Set, b *strings.Builder, doc *spec.Doc, cluster *matter.Cluster, formatter conformanceEntityFormatter) { - for _, c := range cs { - switch c := c.(type) { - case *conformance.Mandatory: - if c.Expression == nil { - b.WriteString("M") - continue - } - renderExpression(b, doc, cluster, c.Expression, formatter) - case *conformance.Optional: - if c.Expression == nil { - b.WriteString("O") - if c.Choice != nil { - b.WriteRune('.') - b.WriteString(c.Choice.ASCIIDocString()) - } - continue - } - b.WriteRune('[') - renderExpression(b, doc, cluster, c.Expression, formatter) - b.WriteRune(']') - if c.Choice != nil { - b.WriteRune('.') - b.WriteString(c.Choice.ASCIIDocString()) - } +func picsConformanceHelper(doc conformance.ReferenceStore, cluster matter.Cluster, cs conformance.Set) raymond.SafeString { + var b strings.Builder + renderConformance(cs, &b, doc, &cluster, entityPICS) + return raymond.SafeString(b.String()) +} + +func conformanceHelper(doc conformance.ReferenceStore, cluster matter.Cluster, cs conformance.Set) raymond.SafeString { + var b strings.Builder + renderConformance(cs, &b, doc, &cluster, entityVariable) + return raymond.SafeString(b.String()) +} + +func renderChoice(c *conformance.Optional, b *strings.Builder) { + // PICS tool does not support + style conformances, so unless this is a "pick one" choice, + //render as fully optional, we'll check the choice conformance properly in the tests. + o := conformance.ChoiceExactLimit{Limit: 1} + if c.Choice != nil && o.Equal(c.Choice.Limit) { + b.WriteRune('.') + b.WriteString(c.Choice.ASCIIDocString()) + } +} + +func renderConformance(cs conformance.Set, b *strings.Builder, doc conformance.ReferenceStore, cluster *matter.Cluster, formatter conformanceEntityFormatter) { + // PICS tool can't handle otherwise conformances, so render anything with an otherwise conformance as optional for the purposes of the + // test plan PICS. This can be fully evaluated in the tests. + // The only exception is if it is provisional, which should be rendered as X. + if len(cs) != 1 { + switch cs[0].(type) { + case *conformance.Provisional: + b.WriteRune('X') default: - b.WriteString(fmt.Sprintf("unknown conformance: %T", c)) + b.WriteString("{PICS_S}: O") + } + return + } + switch c := cs[0].(type) { + case *conformance.Mandatory: + if c.Expression == nil { + b.WriteString("{PICS_S}: M") + return + } + renderExpression(b, doc, cluster, c.Expression, formatter) + case *conformance.Optional: + if c.Expression == nil { + b.WriteString("{PICS_S}: O") + renderChoice(c, b) + return } + renderExpression(b, doc, cluster, c.Expression, formatter) + b.WriteString(": O") + renderChoice(c, b) + case *conformance.Provisional: + b.WriteRune('X') + case *conformance.Disallowed: + b.WriteRune('X') + case *conformance.Deprecated: + b.WriteRune('X') + case *conformance.Described: + b.WriteString("{PICS_S}: O") + default: + b.WriteString(fmt.Sprintf("unknown conformance: %T", c)) } } -func renderExpression(b *strings.Builder, doc *spec.Doc, cluster *matter.Cluster, exp conformance.Expression, formatter conformanceEntityFormatter) { +func renderExpression(b *strings.Builder, doc conformance.ReferenceStore, cluster *matter.Cluster, exp conformance.Expression, formatter conformanceEntityFormatter) { switch exp := exp.(type) { case *conformance.EqualityExpression: b.WriteRune('(') @@ -74,7 +108,7 @@ func renderExpression(b *strings.Builder, doc *spec.Doc, cluster *matter.Cluster b.WriteString(renderReference(doc, exp.Reference, formatter)) case *conformance.LogicalExpression: if exp.Not { - b.WriteRune('!') + b.WriteString("NOT") } b.WriteRune('(') renderExpression(b, doc, cluster, exp.Left, formatter) diff --git a/testplan/constraint.go b/testplan/constraint.go new file mode 100644 index 00000000..ffa17ef5 --- /dev/null +++ b/testplan/constraint.go @@ -0,0 +1,120 @@ +package testplan + +import ( + "fmt" + "strconv" + + "github.com/iancoleman/strcase" + "github.com/mailgun/raymond/v2" + "github.com/project-chip/alchemy/matter" + "github.com/project-chip/alchemy/matter/constraint" + "github.com/project-chip/alchemy/matter/types" +) + +func ifIsConstraintHelper(c constraint.Constraint, cons string, options *raymond.Options) raymond.SafeString { + var isConstraint bool + switch cons { + case "all": + _, isConstraint = c.(*constraint.AllConstraint) + case "described": + _, isConstraint = c.(*constraint.DescribedConstraint) + case "exact": + _, isConstraint = c.(*constraint.ExactConstraint) + case "generic": + _, isConstraint = c.(*constraint.GenericConstraint) + case "list": + _, isConstraint = c.(*constraint.ListConstraint) + case "max": + _, isConstraint = c.(*constraint.MaxConstraint) + case "min": + _, isConstraint = c.(*constraint.MinConstraint) + case "range": + _, isConstraint = c.(*constraint.RangeConstraint) + } + if isConstraint { + return raymond.SafeString(options.Fn()) + } + return raymond.SafeString(options.Inverse()) +} + +func constraintHelper(c constraint.Constraint, dataType types.DataType) raymond.SafeString { + if c == nil { + return raymond.SafeString("") + } + switch c.(type) { + case *constraint.AllConstraint, *constraint.ExactConstraint: + return raymond.SafeString("") + case *constraint.RangeConstraint: + return raymond.SafeString("{valrange} " + c.ASCIIDocString(&dataType)) + } + return raymond.SafeString("") + //return raymond.SafeString(fmt.Sprintf("unimplemented constraint type: %T", c)) +} + +func listConstraintHelper(c constraint.Constraint) raymond.SafeString { + if c == nil { + return raymond.SafeString("") + } + switch c.(type) { + case *constraint.AllConstraint, *constraint.ExactConstraint: + return raymond.SafeString("") + case *constraint.RangeConstraint: + return raymond.SafeString("{valrange}") + } + return raymond.SafeString("") + //return raymond.SafeString(fmt.Sprintf("unimplemented constraint type: %T", c)) +} + +func limitString(cluster *matter.Cluster, limit constraint.Limit) string { + switch limit := limit.(type) { + case *constraint.BooleanLimit: + return strconv.FormatBool(limit.Value) + case *constraint.ExpLimit: + return fmt.Sprintf("%d^%d^", limit.Value, limit.Exp) + case *constraint.HexLimit: + return fmt.Sprintf("0x%X", limit.Value) + case *constraint.IntLimit: + return strconv.FormatInt(limit.Value, 10) + case *constraint.TemperatureLimit: + return fmt.Sprintf("%s°C", limit.Value.String()) + case *constraint.ReferenceLimit: + ref, ok := cluster.Identifier(limit.Value) + if !ok { + return fmt.Sprintf("ERR: unknown reference %s", limit.Value) + } + switch ref := ref.(type) { + case *matter.Field: + return fmt.Sprintf("{A_%s}", strcase.ToScreamingSnake(ref.Name)) + default: + return fmt.Sprintf("ERR: unknown reference type %T (%s)", ref, limit.Value) + } + default: + return fmt.Sprintf("unknown limit type: %T", limit) + } +} + +func limitHelper(cluster *matter.Cluster, l constraint.Limit) raymond.SafeString { + return raymond.SafeString(limitString(cluster, l)) +} + +func minimumHelper(cluster *matter.Cluster, c constraint.Constraint) raymond.SafeString { + switch c := c.(type) { + case *constraint.RangeConstraint: + return raymond.SafeString(limitString(cluster, c.Minimum)) + case *constraint.MinConstraint: + return raymond.SafeString(limitString(cluster, c.Minimum)) + default: + return raymond.SafeString(fmt.Sprintf("unknown minimum constraint: %T", c)) + } +} + +func maximumHelper(cluster *matter.Cluster, c constraint.Constraint) raymond.SafeString { + switch c := c.(type) { + case *constraint.RangeConstraint: + return raymond.SafeString(limitString(cluster, c.Maximum)) + case *constraint.MaxConstraint: + return raymond.SafeString(limitString(cluster, c.Maximum)) + default: + return raymond.SafeString(fmt.Sprintf("unknown maximum constraint: %T", c)) + } +} diff --git a/testplan/events.go b/testplan/events.go deleted file mode 100644 index c9cd73fb..00000000 --- a/testplan/events.go +++ /dev/null @@ -1,48 +0,0 @@ -package testplan - -import ( - "fmt" - "strings" - - "github.com/project-chip/alchemy/matter/spec" -) - -func renderEvents(doc *spec.Doc, cut *clusterUnderTest, b *strings.Builder) { - if len(cut.events) == 0 { - return - } - b.WriteString("==== Events\n\n") - names := make([]string, 0, len(cut.events)) - var longest int - for _, event := range cut.events { - name := entityIdentifier(event) - if len(name) > longest { - longest = len(name) - } - names = append(names, name) - } - for i, name := range names { - b.WriteString(":") - b.WriteString(fmt.Sprintf("%-*s", longest, name)) - b.WriteString(": ") - b.WriteString(cut.events[i].Name) - b.WriteRune('\n') - } - b.WriteRune('\n') - for i, name := range names { - b.WriteString(fmt.Sprintf(":PICS_S%-*s : {PICS_S}.A%s({%s})\n", longest, name, cut.events[i].ID.HexString(), name)) - } - b.WriteString("\n\n|===\n") - b.WriteString("| *Variable* | *Description* | *Mandatory/Optional* | *Notes/Additional Constraints*\n") - for i, event := range cut.events { - name := names[i] - b.WriteString(fmt.Sprintf("| {PICS_S%s} | {devimp} sending the _{%s}_ event?| ", name, name)) - if len(event.Conformance) > 0 { - b.WriteString("{PICS_S}: ") - renderPicsConformance(b, doc, cut.cluster, event.Conformance) - } - b.WriteString(" |\n") - } - b.WriteString("|===\n\n") - -} diff --git a/testplan/features.go b/testplan/features.go index 49c9cfc9..02238e3a 100644 --- a/testplan/features.go +++ b/testplan/features.go @@ -1,37 +1,14 @@ package testplan import ( - "fmt" - "strings" - - "github.com/project-chip/alchemy/matter/spec" + "github.com/project-chip/alchemy/matter/conformance" ) -func renderFeatures(doc *spec.Doc, cut *clusterUnderTest, b *strings.Builder) { - if cut.features == nil || len(cut.features) == 0 { - return - } - - b.WriteString("==== Features\n\n// FeatureMap defined macros\n") - for _, f := range cut.features { - b.WriteString(fmt.Sprintf(":F_%s: %s\n", f.Code, f.Code)) - } - b.WriteRune('\n') - for i, f := range cut.features { - b.WriteString(fmt.Sprintf(":PICS_SF_%s: {PICS_S}.F%02d({F_%s})\n", f.Code, i, f.Code)) - } - b.WriteRune('\n') - b.WriteString("|===\n") - b.WriteString("| *Variable* | *Description* | *Mandatory/Optional* | *Notes/Additional Constraints*\n") - for _, f := range cut.features { - b.WriteString("| {PICS_SF_") - b.WriteString(f.Code) - b.WriteString("} | {devsup} ") - b.WriteString(f.Summary()) - b.WriteString(" | ") - renderFeatureConformance(b, doc, cut.cluster, f.Conformance()) - b.WriteString(" | \n") - } - b.WriteString("|===\n\n\n") - +type feature struct { + From uint64 + To uint64 + Bits []uint64 + Code string + Summary string + Conformance conformance.Set } diff --git a/testplan/generate.go b/testplan/generate.go index 872701cf..3863c637 100644 --- a/testplan/generate.go +++ b/testplan/generate.go @@ -10,18 +10,38 @@ import ( "strings" "github.com/iancoleman/strcase" + "github.com/mailgun/raymond/v2" + "github.com/project-chip/alchemy/errata" "github.com/project-chip/alchemy/internal/pipeline" "github.com/project-chip/alchemy/internal/text" "github.com/project-chip/alchemy/matter" + "github.com/project-chip/alchemy/matter/conformance" "github.com/project-chip/alchemy/matter/spec" "github.com/project-chip/alchemy/matter/types" ) +type GeneratorOption func(g *Generator) + type Generator struct { testPlanRoot string + templateRoot string overwrite bool } +func NewGenerator(testPlanRoot string, overwrite bool, options ...GeneratorOption) *Generator { + g := &Generator{testPlanRoot: testPlanRoot, overwrite: overwrite} + for _, o := range options { + o(g) + } + return g +} + +func TemplateRoot(templateRoot string) func(*Generator) { + return func(g *Generator) { + g.templateRoot = templateRoot + } +} + func (sp Generator) Name() string { return "Generating test plans" } @@ -42,6 +62,12 @@ func (sp *Generator) Process(cxt context.Context, input *pipeline.Data[*spec.Doc destinations := buildDestinations(sp.testPlanRoot, entities, doc.Errata().TestPlan) + var t *raymond.Template + t, err = sp.loadTemplate() + if err != nil { + return + } + for newPath, cluster := range destinations { _, err = os.ReadFile(newPath) @@ -50,8 +76,37 @@ func (sp *Generator) Process(cxt context.Context, input *pipeline.Data[*spec.Doc continue } + var cut *clusterUnderTest + cut, err = filterCluster(doc, cluster) + if err != nil { + err = fmt.Errorf("failed filtering %s: %w", path, err) + return + } + + tc := templateContext{ReferenceStore: conformance.ReferenceStore(doc)} + + args := map[string]any{ + "cluster": cluster, + "context": tc, + "doc": doc, + "features": cut.features, + "attributes": cut.attributes, + "commandsAccepted": cut.commandsAccepted, + "commandsGenerated": cut.commandsGenerated, + "events": cut.events, + } + + if len(cluster.Revisions) > 0 { + args["lastRevision"] = cluster.Revisions[len(cluster.Revisions)-1] + } + args["attributeOptions"] = getOptionality(cut.attributes, func(a *matter.Field) string { return a.Name }, func(a *matter.Field) conformance.Set { return a.Conformance }) + args["eventOptions"] = getOptionality(cut.events, func(a *matter.Event) string { return a.Name }, func(a *matter.Event) conformance.Set { return a.Conformance }) + args["commandsAcceptedOptions"] = getOptionality(cut.commandsAccepted, func(a *matter.Command) string { return a.Name }, func(a *matter.Command) conformance.Set { return a.Conformance }) + args["commandsGeneratedOptions"] = getOptionality(cut.commandsGenerated, func(a *matter.Command) string { return a.Name }, func(a *matter.Command) conformance.Set { return a.Conformance }) + var result string - result, err = renderClusterTestPlan(doc, cluster) + result, err = t.Exec(args) + //result, err = renderClusterTestPlan(doc, cluster) if err != nil { err = fmt.Errorf("failed rendering %s: %w", path, err) return @@ -62,10 +117,6 @@ func (sp *Generator) Process(cxt context.Context, input *pipeline.Data[*spec.Doc return } -func NewGenerator(testPlanRoot string, overwrite bool) *Generator { - return &Generator{testPlanRoot: testPlanRoot, overwrite: overwrite} -} - func getTestPlanPath(testplanRoot string, name string) string { return filepath.Join(testplanRoot, "src/cluster/", name+".adoc") } @@ -87,3 +138,33 @@ func testPlanName(path string, entities []types.Entity) string { } return strcase.ToKebab(name) } + +func buildDestinations(testplanRoot string, entities []types.Entity, errata errata.TestPlan) (destinations map[string]*matter.Cluster) { + destinations = make(map[string]*matter.Cluster) + + for _, e := range entities { + switch e := e.(type) { + case *matter.ClusterGroup: + for _, c := range e.Clusters { + destinations[getTestPlanPathForCluster(testplanRoot, c, errata)] = c + } + case *matter.Cluster: + destinations[getTestPlanPathForCluster(testplanRoot, e, errata)] = e + } + } + return + +} + +func getTestPlanPathForCluster(testplanRoot string, cluster *matter.Cluster, errata errata.TestPlan) string { + if len(errata.TestPlanPaths) > 0 { + tpp, ok := errata.TestPlanPaths[cluster.Name] + if ok { + return filepath.Join(testplanRoot, tpp.Path) + } + } + if errata.TestPlanPath != "" { + return filepath.Join(testplanRoot, errata.TestPlanPath) + } + return getTestPlanPath(testplanRoot, strings.ToLower(strcase.ToSnake(cluster.Name))) +} diff --git a/testplan/global.go b/testplan/global.go index b18ec513..61ffdcaf 100644 --- a/testplan/global.go +++ b/testplan/global.go @@ -1,357 +1,53 @@ package testplan import ( - "fmt" - "log/slog" - "strings" - - "github.com/project-chip/alchemy/matter" "github.com/project-chip/alchemy/matter/conformance" - "github.com/project-chip/alchemy/matter/spec" + "github.com/project-chip/alchemy/matter/types" ) -var globalHeader = ` -== Test Cases -// ################# TEST CASE TEMPLATE: START ################# -''' -=== Generic Test Cases -''' -// ################# GLOBAL ATTRIBUTES TEST CASE: START ################# -==== [TC-{picsCode}-1.1] Global Attributes with {DUT_Server} - -===== Category -Functional conformance. - -===== Purpose -This test case verifies the behavior of the global attributes of the cluster server. - -===== PICS - -* {PICS_S} - -===== Required Devices -:reqDevices: reqDevices_C_TH_and_S_DUT -include::../common/required_devices.adoc[] - -` - -func renderGlobalAttributesTestCase(doc *spec.Doc, cut *clusterUnderTest, b *strings.Builder) (err error) { - b.WriteString(globalHeader) - - b.WriteString("===== Test Procedure\n") - b.WriteString("[cols=\"5%,5%,10%,40%,40%\"]\n") - b.WriteString("|===\n") - b.WriteString("| **#** | *Ref* | *PICS* | *Test Step* | *Expected Outcome* \n") - b.WriteString("| 1 | | | {comDutTH}. |\n") - if len(cut.cluster.Revisions) > 0 { - revision := cut.cluster.Revisions[len(cut.cluster.Revisions)-1] - b.WriteString(fmt.Sprintf("| 2 | {REF_CLUSTERREVISION} | | {THread} _ClusterRevision_ attribute. | {DUTreply} the _ClusterRevision_ attribute and has the value %s.\n", revision.Number)) - } - if len(cut.features) > 0 { - b.WriteString("| 3 | {REF_FEATUREMAP} | | {THread} _FeatureMap_ attribute. | {DUTreply} the _FeatureMap_ attribute and have the following bits set: \n") - for _, f := range cut.features { - var from, to uint64 - - from, to, err = f.Bits() - if err != nil { - return - } - for i := from; i <= to; i++ { - b.WriteString(fmt.Sprintf("- bit %d: {shallBeOneIff} {PICS_SF_%s}\n", i, f.Code)) - } - } - b.WriteString("+\n{remainingBitsZero}\n") - } - writeAttributeListAttribute(b, doc, cut) - writeEventListAttribute(b, doc, cut) - writeAcceptedCommandListAttribute(b, doc, cut) - writeGeneratedCommandListAttribute(b, doc, cut) - b.WriteString(`|=== - -===== Notes/Testing Considerations -^*^ Step 5 is currently not supported and SHALL be skipped. -// ################# GLOBAL ATTRIBUTES TEST CASE: END ################# - -// ################# TEST CASE TEMPLATE: END ################# -`) - return +type optionality[T types.Entity] struct { + Mandatory []T + Optional []T + Features []conformanceOptional[T] } -func writeAttributeListAttribute(b *strings.Builder, doc *spec.Doc, cut *clusterUnderTest) { - b.WriteString("| 4 | {REF_ATTRIBUTELIST} | | {THread} _AttributeList_ attribute. | {DUTreply} the _AttributeList_ attribute and have the list of supported attributes\n") - if len(cut.attributes) == 0 { - b.WriteString("{noEntryStdRgn} +\n") - return - } +type conformanceOptional[T types.Entity] struct { + Entity T + Conformance conformance.Set +} - var mandatory, optional, feature []*matter.Field - expressions := make(map[*matter.Field]conformance.Expression) - optionality := make(map[*matter.Field]bool) - for _, a := range cut.attributes { - for _, c := range a.Conformance { +func getOptionality[T types.Entity](list []T, getName func(e T) string, getConformance func(e T) conformance.Set) (o optionality[T]) { + for _, el := range list { + for _, c := range getConformance(el) { switch c := c.(type) { case *conformance.Mandatory: - optionality[a] = false if c.Expression == nil { - mandatory = append(mandatory, a) + o.Mandatory = append(o.Mandatory, el) break } - feature = append(feature, a) - expressions[a] = c.Expression - + co := conformanceOptional[T]{Entity: el} + co.Conformance = append(co.Conformance, c) + o.Features = append(o.Features, co) case *conformance.Optional: - optionality[a] = true if c.Expression == nil { - optional = append(optional, a) + o.Optional = append(o.Optional, el) continue } - feature = append(feature, a) - expressions[a] = c.Expression - default: - continue - } - break - } - } - if len(mandatory) > 0 { - b.WriteString("{mandatoryEntries} +\n - ") - for i, a := range mandatory { - if i > 0 { - b.WriteString(", ") - } - b.WriteString(a.ID.HexString()) - } - b.WriteString(" +\n{attsGlobalNumbers}.\n") - } - for _, a := range optional { - b.WriteString("+ \n{optionalEntries} +\n ") - b.WriteString(fmt.Sprintf("- %s: {shallIncludeIff} {PICS_S%s}\n", a.ID.HexString(), entityIdentifier(a))) - } - if len(feature) > 0 { - b.WriteString(" + \n{featureEntries} +\n") - for _, a := range feature { - b.WriteString(fmt.Sprintf("- %s: {shallIncludeIf} ", a.ID.HexString())) - var conf conformance.Set - exp := expressions[a] - if optionality[a] { - conf = append(conf, &conformance.Mandatory{ + co := conformanceOptional[T]{Entity: el} + co.Conformance = append(co.Conformance, &conformance.Mandatory{ Expression: &conformance.LogicalExpression{ - Operand: "&", Left: exp, + Operand: "&", Left: c.Expression, Right: []conformance.Expression{&conformance.IdentifierExpression{ - ID: a.Name, + ID: getName(el), }}, }, }) - } else { - conf = append(conf, &conformance.Mandatory{Expression: exp}) - } - renderPicsConformance(b, doc, cut.cluster, conf) - b.WriteString(" and {shallNotInclude}.\n") - } - } - -} - -func writeEventListAttribute(b *strings.Builder, doc *spec.Doc, cut *clusterUnderTest) { - b.WriteString("| 5^*^ | {REF_EVENTLIST} | | {THread} _EventList_ attribute. | {DUTreply} the _EventList_ attribute and have the list of supported events:\n") - if len(cut.events) == 0 { - b.WriteString("{noEntryStdRgn} +\n") - return - } - - var mandatory, optional, feature []*matter.Event - expressions := make(map[*matter.Event]conformance.Expression) - optionality := make(map[*matter.Event]bool) - for _, event := range cut.events { - for _, c := range event.Conformance { - switch c := c.(type) { - case *conformance.Mandatory: - optionality[event] = false - if c.Expression == nil { - mandatory = append(mandatory, event) - break - } - feature = append(feature, event) - expressions[event] = c.Expression - - case *conformance.Optional: - optionality[event] = true - if c.Expression == nil { - optional = append(optional, event) - continue - } - feature = append(feature, event) - expressions[event] = c.Expression - + o.Features = append(o.Features, co) default: - slog.Warn("Unable to determine conformance for event", slog.String("clusterName", cut.cluster.Name), slog.String("eventName", event.Name), slog.Any("conformance", c)) continue } break } } - if len(mandatory) > 0 { - b.WriteString("{mandatoryEntries} +\n") - for i, event := range mandatory { - if i > 0 { - b.WriteString(", ") - } - b.WriteString(event.ID.HexString()) - } - b.WriteString("+\n") - } - for _, event := range optional { - b.WriteString("{optionalEntries} +\n") - b.WriteString(fmt.Sprintf("- %s: {shallIncludeIff} {PICS_S_%s} +\n", event.ID.HexString(), entityIdentifier(event))) - } - if len(feature) > 0 { - b.WriteString("{featureEntries} +\n") - for _, event := range feature { - b.WriteString(fmt.Sprintf("- %s: {shallIncludeIf} ", event.ID.HexString())) - var conf conformance.Set - conf = append(conf, event.Conformance...) - if optionality[event] { - conf = append(conf, &conformance.Mandatory{Expression: &conformance.IdentifierExpression{ID: event.Name}}) - } - renderPicsConformance(b, doc, cut.cluster, conf) - b.WriteString(" and {shallNotInclude}. +\n") - } - } - -} - -func writeAcceptedCommandListAttribute(b *strings.Builder, doc *spec.Doc, cluster *clusterUnderTest) { - b.WriteString("| 6 | {REF_ACCEPTEDCOMMANDLIST} | | {THread} _AcceptedCommandList_ attribute. | {DUTreply} the _AcceptedCommandList_ attribute and have the list of Accepted Command:\n") - if len(cluster.commandsAccepted) == 0 { - b.WriteString("{noEntryStdRgn} +\n") - return - } - - var mandatory, optional, feature []*matter.Command - expressions := make(map[*matter.Command]conformance.Expression) - optionality := make(map[*matter.Command]bool) - for _, command := range cluster.commandsAccepted { - for _, c := range command.Conformance { - switch c := c.(type) { - case *conformance.Mandatory: - optionality[command] = false - if c.Expression == nil { - mandatory = append(mandatory, command) - break - } - feature = append(feature, command) - expressions[command] = c.Expression - - case *conformance.Optional: - optionality[command] = true - if c.Expression == nil { - optional = append(optional, command) - continue - } - feature = append(feature, command) - expressions[command] = c.Expression - - default: - slog.Warn("Unable to determine conformance for accepted command", slog.String("clusterName", cluster.cluster.Name), slog.String("commandName", command.Name), slog.Any("conformance", c)) - continue - } - break - } - } - if len(mandatory) > 0 { - b.WriteString("{mandatoryEntries} +\n") - for i, command := range mandatory { - if i > 0 { - b.WriteString(", ") - } - b.WriteString(command.ID.ShortHexString()) - } - b.WriteString(" +\n") - } - for _, command := range optional { - b.WriteString("{optionalEntries} +\n") - b.WriteString(fmt.Sprintf("- %s: {shallIncludeIff} {PICS_S_%s} +\n", command.ID.ShortHexString(), entityIdentifier(command))) - } - if len(feature) > 0 { - b.WriteString("{featureEntries} +\n") - for _, event := range feature { - b.WriteString(fmt.Sprintf("- %s: {shallIncludeIf} ", event.ID.ShortHexString())) - var conf conformance.Set - conf = append(conf, event.Conformance...) - if optionality[event] { - conf = append(conf, &conformance.Mandatory{Expression: &conformance.IdentifierExpression{ID: event.Name}}) - } - renderPicsConformance(b, doc, cluster.cluster, conf) - b.WriteString(" and {shallNotInclude}. +\n") - } - } - -} - -func writeGeneratedCommandListAttribute(b *strings.Builder, doc *spec.Doc, cluster *clusterUnderTest) { - b.WriteString("| 7 | {REF_GENERATEDCOMMANDLIST} | | {THread} _GeneratedCommandList_ attribute. | {DUTreply} the _GeneratedCommandList_ attribute and have the list of Generated Command:\n") - if len(cluster.commandsGenerated) == 0 { - b.WriteString("{noEntryStdRgn} +\n") - return - } - - var mandatory, optional, feature []*matter.Command - expressions := make(map[*matter.Command]conformance.Expression) - optionality := make(map[*matter.Command]bool) - for _, command := range cluster.commandsGenerated { - for _, c := range command.Conformance { - switch c := c.(type) { - case *conformance.Mandatory: - optionality[command] = false - if c.Expression == nil { - mandatory = append(mandatory, command) - break - } - feature = append(feature, command) - expressions[command] = c.Expression - - case *conformance.Optional: - optionality[command] = true - if c.Expression == nil { - optional = append(optional, command) - continue - } - feature = append(feature, command) - expressions[command] = c.Expression - - default: - slog.Warn("Unable to determine conformance for generated command", slog.String("clusterName", cluster.cluster.Name), slog.String("commandName", command.Name), slog.Any("conformance", c)) - continue - } - break - } - } - if len(mandatory) > 0 { - b.WriteString("{mandatoryEntries} +\n") - for i, command := range mandatory { - if i > 0 { - b.WriteString(", ") - } - b.WriteString(command.ID.ShortHexString()) - } - b.WriteString(" +\n") - } - for _, command := range optional { - b.WriteString("{optionalEntries} +\n") - b.WriteString(fmt.Sprintf("- %s: {shallIncludeIff} {PICS_S_%s} +\n", command.ID.ShortHexString(), entityIdentifier(command))) - } - if len(feature) > 0 { - b.WriteString("{featureEntries} +\n") - for _, event := range feature { - b.WriteString(fmt.Sprintf("- %s: {shallIncludeIf} ", event.ID.ShortHexString())) - var conf conformance.Set - conf = append(conf, event.Conformance...) - if optionality[event] { - conf = append(conf, &conformance.Mandatory{Expression: &conformance.IdentifierExpression{ID: event.Name}}) - } - renderPicsConformance(b, doc, cluster.cluster, conf) - b.WriteString(" and {shallNotInclude}. +\n") - } - } - + return } diff --git a/testplan/helpers.go b/testplan/helpers.go new file mode 100644 index 00000000..84989eff --- /dev/null +++ b/testplan/helpers.go @@ -0,0 +1,80 @@ +package testplan + +import ( + "unicode" + "unicode/utf8" + + "github.com/mailgun/raymond/v2" + "github.com/project-chip/alchemy/matter" + "github.com/project-chip/alchemy/matter/types" +) + +func ifHasQualityHelper(q matter.Quality, quality string, options *raymond.Options) raymond.SafeString { + var hasQuality bool + switch quality { + case "nullable": + hasQuality = q.Has(matter.QualityNullable) + case "nonVolatile": + hasQuality = q.Has(matter.QualityNonVolatile) + case "fixed": + hasQuality = q.Has(matter.QualityFixed) + case "scene": + hasQuality = q.Has(matter.QualityScene) + case "reportable": + hasQuality = q.Has(matter.QualityReportable) + case "changedOmitted": + hasQuality = q.Has(matter.QualityChangedOmitted) + case "diagnostics": + hasQuality = q.Has(matter.QualityDiagnostics) + case "singleton": + hasQuality = q.Has(matter.QualitySingleton) + case "largeMessage": + hasQuality = q.Has(matter.QualityLargeMessage) + case "sourceAttribution": + hasQuality = q.Has(matter.QualitySourceAttribution) + case "atomicWrite": + hasQuality = q.Has(matter.QualityAtomicWrite) + case "quieterReporting": + hasQuality = q.Has(matter.QualityQuieterReporting) + } + if hasQuality { + return raymond.SafeString(options.Fn()) + } + return raymond.SafeString(options.Inverse()) +} + +func dataTypeHelper(dt types.DataType) raymond.SafeString { + switch dt.BaseType { + case types.BaseDataTypeCustom: + if dt.Entity != nil { + switch entity := dt.Entity.(type) { + case *matter.Enum: + return raymond.SafeString(entity.Type.Name) + case *matter.Bitmap: + return raymond.SafeString(entity.Type.Name) + } + } + return raymond.SafeString(dt.Name) + case types.BaseDataTypeVoltage, types.BaseDataTypePower, types.BaseDataTypeEnergy, types.BaseDataTypeAmperage: + return "int64" + default: + return raymond.SafeString(dt.Name) + } +} + +func dataTypeArticleHelper(dt types.DataType) raymond.SafeString { + firstLetter, _ := utf8.DecodeRuneInString(string(dataTypeHelper(dt))) + switch unicode.ToLower(firstLetter) { + case 'a', 'e', 'i', 'o', 'u': // Not perfect, but not importing a dictionary for this + return raymond.SafeString("an") + default: + return raymond.SafeString("a") + } +} + +func ifDataTypeIsArrayHelper(dt types.DataType, options *raymond.Options) raymond.SafeString { + if dt.IsArray() { + return raymond.SafeString(options.Fn()) + } + return raymond.SafeString(options.Inverse()) +} diff --git a/testplan/identifiers.go b/testplan/identifiers.go index 67e0ae1a..46ea37dc 100644 --- a/testplan/identifiers.go +++ b/testplan/identifiers.go @@ -2,8 +2,10 @@ package testplan import ( "fmt" + "strings" - "github.com/iancoleman/strcase" + "github.com/mailgun/raymond/v2" + "github.com/project-chip/alchemy/internal/handlebars" "github.com/project-chip/alchemy/matter" "github.com/project-chip/alchemy/matter/types" ) @@ -21,14 +23,53 @@ func entityIdentifier(entity types.Entity) string { case *matter.Field: switch entity.EntityType() { case types.EntityTypeAttribute: - return fmt.Sprintf("A_%s", strcase.ToScreamingSnake(entity.Name)) + return fmt.Sprintf("A_%s", strings.ToUpper(matter.CaseWithSeparator(entity.Name, '_'))) } case *matter.Feature: return fmt.Sprintf("F_%s", entity.Code) case *matter.Event: - return fmt.Sprintf("E_%s", strcase.ToScreamingSnake(entity.Name)) + return fmt.Sprintf("E_%s", strings.ToUpper(matter.CaseWithSeparator(entity.Name, '_'))) case *matter.Command: - return fmt.Sprintf("C_%s", strcase.ToScreamingSnake(entity.Name)) + return fmt.Sprintf("C_%s", strings.ToUpper(matter.CaseWithSeparator(entity.Name, '_'))) } return fmt.Sprintf("UNKNOWN_TYPE_%T", entity) } + +func entityIdentifierHelper(entity types.Entity) raymond.SafeString { + return raymond.SafeString(entityIdentifier(entity)) +} + +func entityIdentifierPaddedHelper(list any, entity types.Entity) raymond.SafeString { + var longest int + for entity := range handlebars.Iterate[types.Entity](list) { + id := entityIdentifier(entity) + if len(id) > longest { + longest = len(id) + } + } + return raymond.SafeString(fmt.Sprintf("%-*s", longest, entityIdentifier(entity))) +} + +func entityIdentifierPaddingHelper(list any, entity types.Entity) raymond.SafeString { + var longest int + for entity := range handlebars.Iterate[types.Entity](list) { + id := entityIdentifier(entity) + if len(id) > longest { + longest = len(id) + } + } + id := entityIdentifier(entity) + return raymond.SafeString(strings.Repeat(" ", longest-len(id))) +} + +func idHelper(id matter.Number, options *raymond.Options) raymond.SafeString { + format := options.HashStr("format") + if format == "" { + format = "%04X" + } + return raymond.SafeString(fmt.Sprintf(format, id.Value())) +} + +func shortIdHelper(id matter.Number) raymond.SafeString { + return raymond.SafeString(fmt.Sprintf("%02X", id.Value())) +} diff --git a/testplan/server.go b/testplan/server.go deleted file mode 100644 index 1b869b95..00000000 --- a/testplan/server.go +++ /dev/null @@ -1,18 +0,0 @@ -package testplan - -import ( - "strings" - - "github.com/project-chip/alchemy/matter/spec" -) - -func renderServer(doc *spec.Doc, cluster *clusterUnderTest, b *strings.Builder) (err error) { - - b.WriteString("=== Server\n\n") - renderFeatures(doc, cluster, b) - renderAttributes(doc, cluster, b) - renderCommands(doc, cluster, b) - renderEvents(doc, cluster, b) - - return -} diff --git a/testplan/template.go b/testplan/template.go new file mode 100644 index 00000000..acf260fa --- /dev/null +++ b/testplan/template.go @@ -0,0 +1,59 @@ +package testplan + +import ( + "embed" + "log/slog" + + "github.com/mailgun/raymond/v2" + "github.com/project-chip/alchemy/internal/handlebars" + "github.com/project-chip/alchemy/internal/pipeline" + "github.com/project-chip/alchemy/matter/conformance" +) + +//go:embed templates +var templateFiles embed.FS + +var template pipeline.Once[*raymond.Template] + +type templateContext struct { + ReferenceStore conformance.ReferenceStore +} + +func (sp *Generator) loadTemplate() (*raymond.Template, error) { + t, err := template.Do(func() (*raymond.Template, error) { + + ov := handlebars.NewOverlay(sp.templateRoot, templateFiles, "templates") + err := ov.Flush() + if err != nil { + slog.Error("Error flushing embedded templates", slog.Any("error", err)) + } + t, err := handlebars.LoadTemplate("{{> test_plan}}", ov) + if err != nil { + return nil, err + } + + handlebars.RegisterCommonHelpers(t) + + t.RegisterHelper("conformance", conformanceHelper) + t.RegisterHelper("picsConformance", picsConformanceHelper) + t.RegisterHelper("constraint", constraintHelper) + t.RegisterHelper("id", idHelper) + t.RegisterHelper("shortId", shortIdHelper) + t.RegisterHelper("entityId", entityIdentifierHelper) + t.RegisterHelper("entityIdPadded", entityIdentifierPaddedHelper) + t.RegisterHelper("entityIdPadding", entityIdentifierPaddingHelper) + t.RegisterHelper("dataType", dataTypeHelper) + t.RegisterHelper("dataTypeArticle", dataTypeArticleHelper) + t.RegisterHelper("ifHasQuality", ifHasQualityHelper) + t.RegisterHelper("ifIsConstraint", ifIsConstraintHelper) + t.RegisterHelper("limit", limitHelper) + t.RegisterHelper("ifDataTypeIsArray", ifDataTypeIsArrayHelper) + t.RegisterHelper("minLimit", minimumHelper) + t.RegisterHelper("maxLimit", maximumHelper) + return t, nil + }) + if err != nil { + return nil, err + } + return t.Clone(), nil +} diff --git a/testplan/templates/attributes/list.handlebars b/testplan/templates/attributes/list.handlebars new file mode 100644 index 00000000..617f3509 --- /dev/null +++ b/testplan/templates/attributes/list.handlebars @@ -0,0 +1,27 @@ +{{#if attributes}} +==== Attributes + + +{{#each attributes}} +:{{entityIdPadded attributes this}} : {{raw this.Name}} +{{/each}} + + +{{#each attributes}} +:PICS_S{{id this.ID}}{{entityIdPadding attributes this}} : {PICS_S}.A{{id this.ID}}({{#brace}}{{entityId this}}{{/brace}}) +{{/each}} + + +{{#each attributes}} +:PICS_S{{id this.ID}}_CONFORMANCE{{entityIdPadding attributes this}} : {PICS_S}.A{{id this.ID}}({{#brace}}{{entityId this}}{{/brace}}) +{{/each}} + + +|=== +| *Variable* | *Description* | *Mandatory/Optional* | *Notes/Additional Constraints* +{{#each attributes}} +| {{#brace}}PICS_S{{entityId this}}{{/brace}}| {devimp} the _{{#brace}}{{entityId this}}{{/brace}}_ attribute?| {{#if this.conformance}}{{picsConformance context.referenceStore cluster this.conformance}}{{/if}} | +{{/each}} +|=== + +{{/if}} \ No newline at end of file diff --git a/testplan/templates/attributes/test.handlebars b/testplan/templates/attributes/test.handlebars new file mode 100644 index 00000000..3a37c115 --- /dev/null +++ b/testplan/templates/attributes/test.handlebars @@ -0,0 +1,38 @@ +// ################# TEST CASE TEMPLATE: START ################# +==== [TC-{picsCode}-2.1] Attributes with Server as DUT +===== Category +Functional conformance + +===== Purpose +This test case verifies the non-global attributes of the {clustername} cluster server. + +===== PICS + +* {PICS_S} + +===== Required Devices +:reqDevices: reqDevices_C_TH_and_S_DUT +include::../common/required_devices.adoc[] + +===== Device Topology + +TH and DUT are on the same fabric. + +===== Test Setup + +{comDutTH}. + +===== Test Procedure +[cols="5%,5%,10%,40%,40%"] +|=== +| **#** | *Ref* | *PICS* | *Test Step* | *Expected Outcome* +| 1 | | | {comDutTH}. | +{{#each attributes}} +| {{add @index 2}} | {{#brace}}REF_{{cluster.PICS}}_S{{entityId this}}{{/brace}} | {{#brace}}PICS_S{{entityId this}}{{/brace}} | {THread} _{{#brace}}{{entityId this}}{{/brace}}_ attribute. | {{> attributes/test_result attribute=this ~}} +{{/each}} +|=== + +===== Notes/Testing Considerations + + +// ################# TEST CASE TEMPLATE: END ################# \ No newline at end of file diff --git a/testplan/templates/attributes/test_result.handlebars b/testplan/templates/attributes/test_result.handlebars new file mode 100644 index 00000000..84c7a4be --- /dev/null +++ b/testplan/templates/attributes/test_result.handlebars @@ -0,0 +1,12 @@ +{{#ifDataTypeIsArray attribute.type}} + - {DUTreply} a list of {{dataType attribute.type.entryType}} entries +{{#ifIsConstraint attribute.constraint "range"}} + - Verify that the list has between {{minLimit context.cluster attribute.constraint}} and {{maxLimit context.cluster attribute.constraint}} entries +{{else ifIsConstraint attribute.constraint "min"}} + - Verify that the list has {{minLimit context.cluster attribute.constraint}} or more entries +{{else ifIsConstraint attribute.constraint "max"}} + - Verify that the list has no more than {{maxLimit context.cluster attribute.constraint}} entries +{{/ifIsConstraint}} +{{else}} + {DUTreply} {{#ifHasQuality attribute.quality "nullable"}}either null or {{/ifHasQuality}}{{dataTypeArticle attribute.type}} {{dataType attribute.type}} value. {{constraint attribute.constraint attribute.type}} +{{/ifDataTypeIsArray}} \ No newline at end of file diff --git a/testplan/templates/commands/list.handlebars b/testplan/templates/commands/list.handlebars new file mode 100644 index 00000000..57dd5f54 --- /dev/null +++ b/testplan/templates/commands/list.handlebars @@ -0,0 +1,42 @@ +{{#if commandsAccepted}} +==== Commands received + + +{{#each commandsAccepted}} +:{{entityIdPadded commandsAccepted this}} : {{raw this.Name}} +{{/each}} + +{{#each commandsAccepted}} +:PICS_S{{entityIdPadded commandsAccepted this}} : {PICS_S}.C{{shortId this.ID}}.Rsp({{#brace}}{{entityId this}}{{/brace}}) +{{/each}} + + +|=== +| *Variable* | *Description* | *Mandatory/Optional* | *Notes/Additional Constraints* +{{#each commandsAccepted}} +| {{#brace}}PICS_S{{entityId this}}{{/brace}} | {devimp} the _{{#brace}}{{entityId this}}{{/brace}}_ command?| {{#if this.conformance}}{{picsConformance context.referenceStore cluster this.conformance}}{{/if}} | +{{/each}} +|=== + +{{/if}} +{{#if commandsGenerated}} +==== Commands generated + + +{{#each commandsGenerated}} +:{{entityIdPadded commandsGenerated this}} : {{raw this.Name}} +{{/each}} + +{{#each commandsGenerated}} +:PICS_S{{entityIdPadded commandsGenerated this}} : {PICS_S}.C{{shortId this.ID}}.Tx({{#brace}}{{entityId this}}{{/brace}}) +{{/each}} + + +|=== +| *Variable* | *Description* | *Mandatory/Optional* | *Notes/Additional Constraints* +{{#each commandsGenerated}} +| {{#brace}}PICS_S{{entityId this}}{{/brace}} | {devimp} sending the _{{#brace}}{{entityId this}}{{/brace}}_ command?| {{#if this.conformance}}{{picsConformance context.referenceStore cluster this.conformance}}{{/if}} | +{{/each}} +|=== + +{{/if}} \ No newline at end of file diff --git a/testplan/templates/events/list.handlebars b/testplan/templates/events/list.handlebars new file mode 100644 index 00000000..3707a236 --- /dev/null +++ b/testplan/templates/events/list.handlebars @@ -0,0 +1,24 @@ +{{#if events}} +==== Events + + +{{#each events}} +:{{entityId this}}{{entityIdPadding events this}}: {{raw this.Name}} +{{/each}} + +{{#each events}} +:PICS_S{{entityId this}}{{entityIdPaddin events this}} : {PICS_S}.E{{id this.ID}}({{#brace}}{{entityId this}}{{/brace}}) +{{/each}} + +{{#each events}} +:PICS_S{{entityId this}}_CONFORMANCE{{entityIdPaddin events this}} : {PICS_S}.E{{id this.ID}}({{#brace}}{{entityId this}}{{/brace}}) +{{/each}} + +|=== +| *Variable* | *Description* | *Mandatory/Optional* | *Notes/Additional Constraints* +{{#each events}} +| {{#brace}}PICS_S{{entityId this}}{{/brace}} | {devimp} sending the _{{#brace}}{{entityId this}}{{/brace}}_ event?| {{#if this.conformance}}{{picsConformance context.referenceStore cluster this.conformance}}{{/if}} | +{{/each}} +|=== + +{{/if}} \ No newline at end of file diff --git a/testplan/templates/features/list.handlebars b/testplan/templates/features/list.handlebars new file mode 100644 index 00000000..3eca9963 --- /dev/null +++ b/testplan/templates/features/list.handlebars @@ -0,0 +1,26 @@ +{{#if features}} +==== Features + +// FeatureMap defined macros +{{#each features}} +:F_{{raw this.Code}}: {{raw this.Code}} +{{/each}} + +{{#each features}} +:PICS_SF_{{raw this.Code}}: {PICS_S}.F{{format @index "%02d"}}({{#brace}}F_{{raw this.Code}}{{/brace}}) +{{/each}} + +{{#each features}} +:PICS_SF_{{raw this.Code}}_CONFORMANCE: {PICS_S}.F{{format @index "%02d"}}({{#brace}}F_{{raw this.Code}}{{/brace}}) +{{/each}} + +|=== +| *Variable* | *Description* | *Mandatory/Optional* | *Notes/Additional Constraints* +{{#each features}} +| {PICS_SF_{{raw this.Code ~}} +} | {devsup} | {{raw this.Summary}} | {{#if this.Conformance}}{{conformance context.referenceStore cluster this.Conformance}}{{/if}} +{{/each}} +|=== + + +{{/if}} \ No newline at end of file diff --git a/testplan/templates/global/attribute.handlebars b/testplan/templates/global/attribute.handlebars new file mode 100644 index 00000000..481cc631 --- /dev/null +++ b/testplan/templates/global/attribute.handlebars @@ -0,0 +1,21 @@ +{{#if list}} +{{#if optionality.mandatory}} +{mandatoryEntries} + +- {{#each optionality.mandatory}}{{#if @first}}0x{{id this.ID format=idFormat}}{{else}}, 0x{{id this.ID format=idFormat}}{{/if}}{{/each}} +{attsGlobalNumbers}. +{{/if}} +{{#if optionality.optional}} +{optionalEntries} + +{{#each optionality.optional}} +- 0x{{id this.ID format=idFormat}}: {shallIncludeIff} {{#brace}}PICS_S{{entityId this}}{{/brace}} +{{/each}} +{{/if}} +{{#if optionality.features}} +{featureEntries} + +{{#each optionality.features}} +- 0x{{id this.entity.ID format=idFormat}}: {shallIncludeIf} {{picsConformance context.referenceStore cluster this.conformance}} and {shallNotInclude}. +{{/each}} +{{/if}} +{{else}} +{noEntryStdRgn} + +{{/if}} \ No newline at end of file diff --git a/testplan/templates/global/attributes.handlebars b/testplan/templates/global/attributes.handlebars new file mode 100644 index 00000000..4a1fc46f --- /dev/null +++ b/testplan/templates/global/attributes.handlebars @@ -0,0 +1,70 @@ +== Test Cases +// ################# TEST CASE TEMPLATE: START ################# +''' +=== Generic Test Cases +''' +// ################# GLOBAL ATTRIBUTES TEST CASE: START ################# +==== [TC-{picsCode}-1.1] Global Attributes with {DUT_Server} + +===== Category +Functional conformance. + +===== Purpose +This test case verifies the behavior of the global attributes of the cluster server. + +===== PICS + +* {PICS_S} + +===== Required Devices +:reqDevices: reqDevices_C_TH_and_S_DUT +include::../common/required_devices.adoc[] + +===== Test Procedure +[cols="5%,5%,10%,40%,40%"] +|=== +| **#** | *Ref* | *PICS* | *Test Step* | *Expected Outcome* +| 1 | | | {comDutTH}. | +{{#if lastRevision}} +| 2 | {REF_CLUSTERREVISION} | | {THread} _ClusterRevision_ attribute. | {DUTreply} the _ClusterRevision_ attribute and has the value {{lastRevision.number}}. +{{/if}} +{{#if features}} +| 3 | {REF_FEATUREMAP} | | {THread} _FeatureMap_ attribute. | {DUTreply} the _FeatureMap_ attribute and have the following bits set: +{{#each features}} +{{> global/feature_codes feature=this}} +{{/each}} ++ +{remainingBitsZero} +{{/if}} +| 4 | {REF_ATTRIBUTELIST} | | {THread} _AttributeList_ attribute. | {DUTreply} the _AttributeList_ attribute and have the list of supported attributes +{{#if attributes}} +{{> global/attribute list=attributes optionality=attributeOptions idFormat="%04X"}} +{{else}} +{noEntryStdRgn} + +{{/if}} +| 5^*^ | {REF_EVENTLIST} | | {THread} _EventList_ attribute. | {DUTreply} the _EventList_ attribute and have the list of supported events: +{{#if events}} +{{> global/attribute list=events optionality=eventOptions}} +{{else}} +{noEntryStdRgn} + +{{/if}} +| 6 | {REF_ACCEPTEDCOMMANDLIST} | | {THread} _AcceptedCommandList_ attribute. | {DUTreply} the _AcceptedCommandList_ attribute and have the list of Accepted Command: +{{#if commandsAccepted}} +{{> global/attribute list=commandsAccepted optionality=commandsAcceptedOptions idFormat="%02X"}} +{{else}} +{noEntryStdRgn} + +{{/if}} +| 7 | {REF_GENERATEDCOMMANDLIST} | | {THread} _GeneratedCommandList_ attribute. | {DUTreply} the _GeneratedCommandList_ attribute and have the list of Generated Command: +{{#if commandsGenerated}} +{{> global/attribute list=commandsGenerated optionality=commandsGeneratedOptions idFormat="%02X"}} +{{else}} +{noEntryStdRgn} + +{{/if}} +|=== + +===== Notes/Testing Considerations +^*^ Step 5 is currently not supported and SHALL be skipped. +// ################# GLOBAL ATTRIBUTES TEST CASE: END ################# + +// ################# TEST CASE TEMPLATE: END ################# + diff --git a/testplan/templates/global/feature_codes.handlebars b/testplan/templates/global/feature_codes.handlebars new file mode 100644 index 00000000..9d4954f2 --- /dev/null +++ b/testplan/templates/global/feature_codes.handlebars @@ -0,0 +1,3 @@ +{{#each feature.bits}} +- bit {{this}}: {shallBeOneIff} {{#brace}}PICS_SF_{{feature.code}}{{/brace}} +{{/each}} \ No newline at end of file diff --git a/testplan/header.go b/testplan/templates/test_plan.handlebars similarity index 64% rename from testplan/header.go rename to testplan/templates/test_plan.handlebars index 78c7c033..36624079 100644 --- a/testplan/header.go +++ b/testplan/templates/test_plan.handlebars @@ -1,13 +1,4 @@ -package testplan - -import ( - "fmt" - "strings" - - "github.com/project-chip/alchemy/matter" -) - -var header = `= *%s Cluster Test Plan* += *{{cluster.Name}} Cluster Test Plan* ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: @@ -26,8 +17,8 @@ endif::[] :doctype: book :author: Matter CSG Test Plans Tiger Team :sectnums: -:picsCode: %s -:clustername: %s +:picsCode: {{cluster.PICS}} +:clustername: {{cluster.Name}} // Common AsciiDocAttributes include::../common/cluster_common.adoc[] @@ -41,13 +32,25 @@ This section covers the {clustername} Cluster Test Plan related PICS items that |=== | *Variable* | *Description* | *Mandatory/Optional* | *Notes/Additional Constraints* | {PICS_S} | {devimp} the {clustername} cluster as a server? | O | +| {PICS_C} | {devimp} the {clustername} cluster as a client? | O | +|=== + +=== Server + +{{> features/list }} +{{> attributes/list }} +{{> commands/list }} +{{> events/list }} +== Test Case List + +|=== +| *TC UUID* | *Test Case Name* +| TC-{picsCode}-1.1 | Global Attributes with {DUT_Server} +| TC-{picsCode}-2.1 | Attributes with Server as DUT +| TC-{picsCode}-2.2 | Primary Functionality with Server as DUT |=== -` -func renderHeader(cluster *matter.Cluster, b *strings.Builder) (err error) { - header := fmt.Sprintf(header, cluster.Name, cluster.PICS, cluster.Name) - _, err = b.WriteString(header) - return -} +{{> global/attributes}} +{{> attributes/test}} \ No newline at end of file diff --git a/testplan/testplan.go b/testplan/testplan.go deleted file mode 100644 index fc1fde56..00000000 --- a/testplan/testplan.go +++ /dev/null @@ -1,36 +0,0 @@ -package testplan - -import ( - "path/filepath" - "strings" - - "github.com/iancoleman/strcase" - "github.com/project-chip/alchemy/errata" - "github.com/project-chip/alchemy/matter" - "github.com/project-chip/alchemy/matter/types" -) - -func buildDestinations(testplanRoot string, entities []types.Entity, errata errata.TestPlan) (destinations map[string]*matter.Cluster) { - destinations = make(map[string]*matter.Cluster) - - for _, e := range entities { - switch e := e.(type) { - case *matter.ClusterGroup: - for _, c := range e.Clusters { - fileName := strings.ToLower(strcase.ToSnake(c.Name)) - newPath := getTestPlanPath(testplanRoot, fileName) - destinations[newPath] = c - } - case *matter.Cluster: - var newPath string - if errata.TestPlanPath != "" { - newPath = filepath.Join(testplanRoot, errata.TestPlanPath) - } else { - newPath = getTestPlanPath(testplanRoot, strings.ToLower(strcase.ToSnake(e.Name))) - } - destinations[newPath] = e - } - } - return - -} diff --git a/zap/configurator.go b/zap/configurator.go index 4c9c5be7..6c6716aa 100644 --- a/zap/configurator.go +++ b/zap/configurator.go @@ -152,11 +152,11 @@ func typeBelongsToOtherCluster(entity types.Entity, parentEntity types.Entity) b var typeParent types.Entity switch entity := entity.(type) { case *matter.Bitmap: - typeParent = entity.ParentEntity + typeParent = entity.Parent() case *matter.Enum: - typeParent = entity.ParentEntity + typeParent = entity.Parent() case *matter.Struct: - typeParent = entity.ParentEntity + typeParent = entity.Parent() } if typeParent == nil { // This is a global type, and doesn't belong to any cluster return true diff --git a/zap/datatype.go b/zap/datatype.go index 95ca25c1..a0c85825 100644 --- a/zap/datatype.go +++ b/zap/datatype.go @@ -550,7 +550,7 @@ func GetDefaultValue(cc *matter.ConstraintContext) (defaultValue types.DataTypeE } defaultValue = c.Default(cc) switch defaultValue.Type { - case types.DataTypeExtremeTypeEmpty: + case types.DataTypeExtremeTypeEmptyList: if !cc.Field.Type.HasLength() { defaultValue = types.DataTypeExtreme{} } diff --git a/zap/generate/conformance.go b/zap/generate/conformance.go index 5c1ef346..6deeb4f2 100644 --- a/zap/generate/conformance.go +++ b/zap/generate/conformance.go @@ -9,6 +9,9 @@ import ( func renderConformance(doc *spec.Doc, identifierStore conformance.IdentifierStore, c conformance.Conformance, parent *etree.Element) error { removeConformance(parent) + if conformance.IsMandatory(c) { + return nil + } return dm.RenderConformanceElement(doc, identifierStore, c, parent) } @@ -18,7 +21,7 @@ func removeConformance(parent *etree.Element) { switch child := child.(type) { case *etree.Element: switch child.Tag { - case "mandatoryConform", "optionalConform", "disableConform", "provisionalConform", "deprecateConform", "otherwiseConform": + case "mandatoryConform", "optionalConform", "disableConform", "disallowConform", "provisionalConform", "deprecateConform", "otherwiseConform": trash = append(trash, child) } } diff --git a/zap/generate/devicetypes.go b/zap/generate/devicetypes.go index 2e3e7a15..53195af2 100644 --- a/zap/generate/devicetypes.go +++ b/zap/generate/devicetypes.go @@ -6,19 +6,12 @@ import ( "log/slog" "os" "path/filepath" - "strconv" - "strings" "github.com/beevik/etree" - "github.com/project-chip/alchemy/errata" - "github.com/project-chip/alchemy/internal/log" "github.com/project-chip/alchemy/internal/pipeline" - "github.com/project-chip/alchemy/internal/xml" "github.com/project-chip/alchemy/matter" - "github.com/project-chip/alchemy/matter/conformance" "github.com/project-chip/alchemy/matter/spec" "github.com/project-chip/alchemy/matter/types" - "github.com/project-chip/alchemy/zap" ) var utilityDevicesMask uint64 = 0xFF000000 @@ -27,9 +20,19 @@ type DeviceTypesPatcher struct { sdkRoot string spec *spec.Specification clusterAliases map[string]string + + generateFeatureXML bool } -func NewDeviceTypesPatcher(sdkRoot string, spec *spec.Specification, clusterAliases pipeline.Map[string, []string]) *DeviceTypesPatcher { +type DeviceTypePatcherOption func(dtp *DeviceTypesPatcher) + +func DeviceTypePatcherGenerateFeatureXML(generate bool) DeviceTypePatcherOption { + return func(dtp *DeviceTypesPatcher) { + dtp.generateFeatureXML = generate + } +} + +func NewDeviceTypesPatcher(sdkRoot string, spec *spec.Specification, clusterAliases pipeline.Map[string, []string], options ...DeviceTypePatcherOption) *DeviceTypesPatcher { dtp := &DeviceTypesPatcher{sdkRoot: sdkRoot, spec: spec, clusterAliases: make(map[string]string)} clusterAliases.Range(func(cluster string, aliases []string) bool { for _, alias := range aliases { @@ -37,6 +40,9 @@ func NewDeviceTypesPatcher(sdkRoot string, spec *spec.Specification, clusterAlia } return true }) + for _, opt := range options { + opt(dtp) + } return dtp } @@ -57,7 +63,7 @@ func (p DeviceTypesPatcher) Process(cxt context.Context, inputs []*pipeline.Data if dt.ID.Valid() { deviceTypesToUpdateByID[dt.ID.Value()] = dt } else { - deviceTypesToUpdateByName[matterDeviceTypeName(dt)] = dt + deviceTypesToUpdateByName[dt.Name] = dt } } } @@ -69,7 +75,7 @@ func (p DeviceTypesPatcher) Process(cxt context.Context, inputs []*pipeline.Data if deviceType.ID.Valid() { allDeviceTypesByID[deviceType.ID.Value()] = deviceType } else { - allDeviceTypesByName[matterDeviceTypeName(deviceType)] = deviceType + allDeviceTypesByName[deviceType.Name] = deviceType } } @@ -166,327 +172,3 @@ func (p DeviceTypesPatcher) Process(cxt context.Context, inputs []*pipeline.Data outputs = append(outputs, pipeline.NewData[[]byte](deviceTypesXMLPath, []byte(out))) return } - -type clusterRequirements struct { - name string - clusterRequirements []*matter.ClusterRequirement - baseClusterRequirements []*matter.ClusterRequirement - elementRequirements []*matter.ElementRequirement -} - -func (p DeviceTypesPatcher) applyDeviceTypeToElement(spec *spec.Specification, deviceType *matter.DeviceType, dte *etree.Element) (err error) { - xml.SetOrCreateSimpleElement(dte, "name", zap.DeviceTypeName(deviceType)) - xml.SetOrCreateSimpleElement(dte, "domain", "CHIP") - xml.SetOrCreateSimpleElement(dte, "typeName", matterDeviceTypeName(deviceType)) - xml.SetOrCreateSimpleElement(dte, "profileId", "0x0103").CreateAttr("editable", "false") - xml.SetOrCreateSimpleElement(dte, "deviceId", deviceType.ID.HexString()).CreateAttr("editable", "false") - xml.SetOrCreateSimpleElement(dte, "class", deviceType.Class) - xml.SetOrCreateSimpleElement(dte, "scope", deviceType.Scope) - clustersElement := dte.SelectElement("clusters") - if len(deviceType.ClusterRequirements) == 0 { - if clustersElement != nil { - dte.RemoveChild(clustersElement) - } - return - } - if clustersElement == nil { - clustersElement = dte.CreateElement("clusters") - } - clusterRequirementsByName := make(map[string]*clusterRequirements) - for _, cr := range deviceType.ClusterRequirements { - name := strings.ToLower(cr.ClusterName) - crr, ok := clusterRequirementsByName[name] - if !ok { - crr = &clusterRequirements{name: cr.ClusterName} - clusterRequirementsByName[name] = crr - } - crr.clusterRequirements = append(crr.clusterRequirements, cr) - } - for _, cr := range spec.BaseDeviceType.ClusterRequirements { - name := strings.ToLower(cr.ClusterName) - crr, ok := clusterRequirementsByName[name] - if !ok { - crr = &clusterRequirements{name: cr.ClusterName} - clusterRequirementsByName[name] = crr - } - crr.baseClusterRequirements = append(crr.baseClusterRequirements, cr) - slog.Debug("adding base device type cluster requirement", slog.String("cluster", cr.ClusterName)) - } - for _, ers := range [][]*matter.ElementRequirement{deviceType.ElementRequirements, spec.BaseDeviceType.ElementRequirements} { - for _, er := range ers { - name := strings.ToLower(er.ClusterName) - crr, ok := clusterRequirementsByName[name] - if !ok { - slog.Warn("element requirement with missing cluster requirement", log.Path("source", deviceType), slog.String("deviceType", deviceType.Name), slog.String("cluster", er.ClusterName)) - continue - } - crr.elementRequirements = append(crr.elementRequirements, er) - } - - } - for _, include := range clustersElement.SelectElements("include") { - ca := include.SelectAttr("cluster") - if ca == nil { - slog.Warn("missing cluster attribute on include", slog.String("deviceTypeId", deviceType.ID.HexString())) - clustersElement.RemoveChild(include) - continue - } - crs, ok := clusterRequirementsByName[strings.ToLower(ca.Value)] - if !ok { - slog.Debug("unknown cluster attribute on include", slog.String("deviceTypeId", deviceType.ID.HexString()), slog.String("clusterName", ca.Value)) - clustersElement.RemoveChild(include) - continue - } - p.setIncludeAttributes(clustersElement, include, spec, deviceType, crs) - delete(clusterRequirementsByName, strings.ToLower(ca.Value)) - } - for _, crs := range [][]*matter.ClusterRequirement{spec.BaseDeviceType.ClusterRequirements, deviceType.ClusterRequirements} { - for _, cr := range crs { - crr, ok := clusterRequirementsByName[strings.ToLower(cr.ClusterName)] - if ok { - p.setIncludeAttributes(clustersElement, nil, spec, deviceType, crr) - delete(clusterRequirementsByName, strings.ToLower(cr.ClusterName)) - } - } - } - return -} - -func (p DeviceTypesPatcher) setIncludeAttributes(clustersElement *etree.Element, include *etree.Element, spec *spec.Specification, deviceType *matter.DeviceType, cr *clusterRequirements) { - cluster, ok := spec.ClustersByName[cr.name] - if !ok { - var alias string - alias, ok = p.clusterAliases[cr.name] - if ok { - cluster, ok = spec.ClustersByName[alias] - if !ok { - slog.Warn("unknown cluster alias on include", slog.String("deviceTypeId", deviceType.ID.HexString()), slog.String("clusterName", cr.name), slog.String("alias", alias)) - return - } - } else { - slog.Warn("unknown cluster on include", slog.String("deviceTypeId", deviceType.ID.HexString()), slog.String("clusterName", cr.name)) - return - } - } - - clusterDoc, ok := spec.DocRefs[cluster] - if !ok { - slog.Warn("unknown doc path on include", slog.String("deviceTypeId", deviceType.ID.HexString()), slog.String("clusterName", cluster.Name)) - - } - errata := errata.GetZAP(clusterDoc.Path.Relative) - cxt := conformance.Context{ - Values: map[string]any{"Matter": true}, - } - - var server, client, clientLocked, serverLocked bool - clientLocked = true - serverLocked = true - for i, crs := range [][]*matter.ClusterRequirement{cr.baseClusterRequirements, cr.clusterRequirements} { - for _, cr := range crs { - conf, err := cr.Conformance.Eval(cxt) - if err != nil { - slog.Warn("Error evaluating conformance of cluster requirement", slog.String("deviceTypeId", deviceType.ID.HexString()), slog.String("clusterName", cluster.Name), slog.Any("error", err)) - continue - } - - switch conf { - case conformance.StateOptional, conformance.StateProvisional: - if i == 0 { // Base cluster requirements only get written when mandatory, apparently? - return - } - case conformance.StateMandatory: - default: - return - } - - switch cr.Interface { - case matter.InterfaceServer: - server = true - serverLocked = conf == conformance.StateMandatory || conf == conformance.StateProvisional - case matter.InterfaceClient: - client = true - clientLocked = conf == conformance.StateMandatory || conf == conformance.StateProvisional - } - } - } - - if include == nil { - include = clustersElement.CreateElement("include") - } - include.CreateAttr("cluster", cr.name) - - xml.SetNonexistentAttr(include, "client", strconv.FormatBool(client)) - xml.SetNonexistentAttr(include, "server", strconv.FormatBool(server)) - xml.SetNonexistentAttr(include, "clientLocked", strconv.FormatBool(clientLocked)) - xml.SetNonexistentAttr(include, "serverLocked", strconv.FormatBool(serverLocked)) - - requiredAttributes := make(map[string]struct{}) - requiredAttributeDefines := make(map[string]struct{}) - requiredCommands := make(map[string]struct{}) - requiredCommandFields := make(map[string]map[string]struct{}) - requiredEvents := make(map[string]struct{}) - - for _, er := range cr.elementRequirements { - conf, err := er.Conformance.Eval(cxt) - if err != nil { - slog.Warn("Error evaluating conformance of element requirement", slog.String("deviceTypeId", deviceType.ID.HexString()), slog.String("clusterName", cluster.Name), slog.Any("error", err)) - continue - } - if conf == conformance.StateMandatory || conf == conformance.StateProvisional { - switch er.Element { - case types.EntityTypeFeature: - cxt.Values[er.Name] = true - case types.EntityTypeAttribute: - requiredAttributes[er.Name] = struct{}{} - cxt.Values[er.Name] = true - case types.EntityTypeCommand: - requiredCommands[er.Name] = struct{}{} - cxt.Values[er.Name] = true - case types.EntityTypeEvent: - requiredEvents[er.Name] = struct{}{} - cxt.Values[er.Name] = true - case types.EntityTypeCommandField: - cf, ok := requiredCommandFields[er.Name] - if !ok { - cf = make(map[string]struct{}) - requiredCommandFields[er.Name] = cf - cxt.Values[er.Name] = true - } - cf[er.Field] = struct{}{} - default: - slog.Warn("Element requirement with unrecognized element type", slog.String("deviceType", deviceType.Name), slog.String("entityType", er.Element.String())) - } - } - } - - for _, attr := range cluster.Attributes { - _, required := requiredAttributes[attr.Name] - if !required { - conf, err := attr.Conformance.Eval(cxt) - if err != nil { - slog.Debug("Error evaluating conformance of attribute", slog.String("deviceTypeId", deviceType.ID.HexString()), slog.String("clusterName", cluster.Name), slog.Any("error", err)) - continue - - } - if conf == conformance.StateMandatory || conf == conformance.StateProvisional { - required = true - } - - } - if required { - requiredAttributeDefines[getDefine(attr.Name, errata.ClusterDefinePrefix, errata)] = struct{}{} - } - } - for _, cmd := range cluster.Commands { - conf, err := cmd.Conformance.Eval(cxt) - if err != nil { - slog.Warn("Error evaluating conformance of command", slog.String("deviceTypeId", deviceType.ID.HexString()), slog.String("clusterName", cluster.Name), slog.Any("error", err)) - continue - - } - if conf == conformance.StateMandatory || conf == conformance.StateProvisional { - requiredCommands[cmd.Name] = struct{}{} - } - } - for _, ev := range cluster.Events { - conf, err := ev.Conformance.Eval(cxt) - if err != nil { - slog.Warn("Error evaluating conformance of event", slog.String("deviceTypeId", deviceType.ID.HexString()), slog.String("clusterName", cluster.Name), slog.Any("error", err)) - continue - - } - if conf == conformance.StateMandatory || conf == conformance.StateProvisional { - requiredEvents[ev.Name] = struct{}{} - } - } - ras := include.SelectElements("requireAttribute") - for _, ra := range ras { - rat := ra.Text() - _, required := requiredAttributeDefines[rat] - if required { - delete(requiredAttributeDefines, rat) - } else { - include.RemoveChild(ra) - } - } - for ra := range requiredAttributeDefines { - rae := etree.NewElement("requireAttribute") - rae.SetText(ra) - xml.InsertElementByName(include, rae, "") - } - rcs := include.SelectElements("requireCommand") - for _, rc := range rcs { - rct := rc.Text() - _, required := requiredCommands[rct] - if required { - delete(requiredCommands, rct) - } else { - include.RemoveChild(rc) - } - } - for ra := range requiredCommands { - rae := etree.NewElement("requireCommand") - rae.SetText(ra) - xml.InsertElementByName(include, rae, "requireAttribute") - } - rcfs := include.SelectElements("requireCommandField") - for _, rc := range rcfs { - rcfc := rc.SelectElement("command") - if rcfc == nil { - include.RemoveChild(rc) - continue - } - rct := rcfc.Text() - fields, required := requiredCommandFields[rct] - if required { - delete(requiredCommandFields, rct) - } else { - include.RemoveChild(rc) - continue - } - rcffs := rc.SelectElements("field") - for _, rcff := range rcffs { - rcft := rc.Text() - _, required := fields[rcft] - if required { - delete(fields, rcft) - } else { - rc.RemoveChild(rcff) - } - } - for rcft := range fields { - rae := etree.NewElement("field") - rae.SetText(rcft) - xml.InsertElementByName(rc, rae, "command") - } - } - for rcfc, rcffs := range requiredCommandFields { - rcfe := etree.NewElement("requireCommandField") - rcfce := rcfe.CreateElement("command") - rcfce.SetText(rcfc) - for rcff := range rcffs { - rcfe.CreateElement("field").SetText(rcff) - } - xml.InsertElementByName(include, rcfe, "requireAttribute", "requireCommand") - } - res := include.SelectElements("requireEvent") - for _, re := range res { - ret := re.Text() - _, required := requiredEvents[ret] - if required { - delete(requiredEvents, ret) - } else { - include.RemoveChild(re) - } - } - for ra := range requiredEvents { - rae := etree.NewElement("requireEvent") - rae.SetText(ra) - xml.InsertElementByName(include, rae, "requireAttribute", "requireCommand", "requireCommandField") - } -} - -func matterDeviceTypeName(deviceType *matter.DeviceType) string { - return fmt.Sprintf("Matter %s", deviceType.Name) -} diff --git a/zap/generate/namespaces.go b/zap/generate/namespaces.go index 3a4a2c1e..869467c6 100644 --- a/zap/generate/namespaces.go +++ b/zap/generate/namespaces.go @@ -10,6 +10,7 @@ import ( "github.com/beevik/etree" "github.com/project-chip/alchemy/internal/pipeline" "github.com/project-chip/alchemy/internal/text" + "github.com/project-chip/alchemy/internal/xml" "github.com/project-chip/alchemy/matter" "github.com/project-chip/alchemy/matter/spec" ) @@ -42,13 +43,13 @@ func (p NamespacePatcher) Process(cxt context.Context, inputs []*pipeline.Data[[ return } - xml := etree.NewDocument() - err = xml.ReadFromBytes(namespaceXML) + doc := etree.NewDocument() + err = doc.ReadFromBytes(namespaceXML) if err != nil { return } - configurator := xml.SelectElement("configurator") + configurator := doc.SelectElement("configurator") if configurator == nil { err = fmt.Errorf("missing configurator element in %s", namespaceXMLPath) return @@ -78,7 +79,7 @@ func (p NamespacePatcher) Process(cxt context.Context, inputs []*pipeline.Data[[ } for name, ns := range namespacesByName { - nse := configurator.CreateElement("enum") + nse := etree.NewElement("enum") nse.CreateAttr("name", name) nse.CreateAttr("type", "enum8") for _, val := range ns.SemanticTags { @@ -86,11 +87,12 @@ func (p NamespacePatcher) Process(cxt context.Context, inputs []*pipeline.Data[[ ve.CreateAttr("value", val.ID.ShortHexString()) ve.CreateAttr("name", val.Name) } + xml.InsertElementByAttribute(configurator, nse, "name") } var out string - xml.Indent(2) - out, err = xml.WriteToString() + doc.Indent(2) + out, err = doc.WriteToString() if err != nil { return } diff --git a/zap/generate/requirements.go b/zap/generate/requirements.go new file mode 100644 index 00000000..aaaf4144 --- /dev/null +++ b/zap/generate/requirements.go @@ -0,0 +1,527 @@ +package generate + +import ( + "log/slog" + "strconv" + "strings" + + "github.com/beevik/etree" + "github.com/project-chip/alchemy/errata" + "github.com/project-chip/alchemy/internal/find" + "github.com/project-chip/alchemy/internal/xml" + "github.com/project-chip/alchemy/matter" + "github.com/project-chip/alchemy/matter/conformance" + "github.com/project-chip/alchemy/matter/spec" + "github.com/project-chip/alchemy/matter/types" + "github.com/project-chip/alchemy/zap" +) + +type clusterRequirements struct { + id *matter.Number + name string + requirementsFromDeviceType []*matter.ClusterRequirement + requirementsFromBaseDeviceType []*matter.ClusterRequirement + elementRequirements []*matter.ElementRequirement +} + +func (p DeviceTypesPatcher) applyDeviceTypeToElement(spec *spec.Specification, deviceType *matter.DeviceType, dte *etree.Element) (err error) { + xml.SetOrCreateSimpleElement(dte, "name", zap.DeviceTypeName(deviceType)) + xml.SetOrCreateSimpleElement(dte, "domain", "CHIP", "name") + xml.SetOrCreateSimpleElement(dte, "typeName", deviceType.Name, "name", "domain") + xml.SetOrCreateSimpleElement(dte, "profileId", "0x0103", "name", "domain", "typeName").CreateAttr("editable", "false") + xml.SetOrCreateSimpleElement(dte, "deviceId", deviceType.ID.HexString(), "name", "domain", "typeName", "profileId").CreateAttr("editable", "false") + xml.SetOrCreateSimpleElement(dte, "class", deviceType.Class, "name", "domain", "typeName", "profileId", "deviceId") + xml.SetOrCreateSimpleElement(dte, "scope", deviceType.Scope, "name", "domain", "typeName", "profileId", "deviceId", "class") + clustersElement := dte.SelectElement("clusters") + if len(deviceType.ClusterRequirements) == 0 { + if clustersElement != nil { + dte.RemoveChild(clustersElement) + } + return + } + if clustersElement == nil { + clustersElement = dte.CreateElement("clusters") + } + clusterRequirementsByID := make(map[uint64]*clusterRequirements) + + var hasClient, hasServer bool + + for _, cr := range deviceType.ClusterRequirements { + if !cr.ClusterID.Valid() { + continue + } + if conformance.IsMandatory(cr.Conformance) { + switch cr.Interface { + case matter.InterfaceClient: + hasClient = true + case matter.InterfaceServer: + hasServer = true + } + } + if hasClient && hasServer { + break + } + } + cxt := conformance.Context{ + Values: map[string]any{ + "Matter": true, + deviceType.Class: true, + "Client": hasClient, + "Server": hasServer, + }, + } + for _, cr := range deviceType.ClusterRequirements { + if !cr.ClusterID.Valid() { + continue + } + crr, ok := clusterRequirementsByID[cr.ClusterID.Value()] + if !ok { + crr = &clusterRequirements{id: cr.ClusterID, name: cr.ClusterName} + clusterRequirementsByID[cr.ClusterID.Value()] = crr + } + + crr.requirementsFromDeviceType = append(crr.requirementsFromDeviceType, cr) + } + for _, er := range deviceType.ElementRequirements { + if !er.ClusterID.Valid() { + continue + } + crr, ok := clusterRequirementsByID[er.ClusterID.Value()] + if !ok { + // The spec has an element requirement for a cluster that wasn't required; probably a mistake, so + // let's pretend the cluster was in the requirements, but optional + crr = &clusterRequirements{id: er.ClusterID, name: er.ClusterName} + clusterRequirementsByID[er.ClusterID.Value()] = crr + } + crr.elementRequirements = append(crr.elementRequirements, er) + } + for _, er := range spec.BaseDeviceType.ElementRequirements { + if !er.ClusterID.Valid() { + continue + } + crr, ok := clusterRequirementsByID[er.ClusterID.Value()] + if ok { + // If the Base Device Type has an element requirement for a cluster required by the Device Type, then include its element requirements too + crr.elementRequirements = append(crr.elementRequirements, er) + } + } + + for _, cr := range spec.BaseDeviceType.ClusterRequirements { + if !cr.ClusterID.Valid() { + continue + } + crr, ok := clusterRequirementsByID[cr.ClusterID.Value()] + if ok { + // If a Base Device Type requirement specifies a cluster also specified by the device type, then include the relevant Base Device type requirement + crr.requirementsFromBaseDeviceType = append(crr.requirementsFromBaseDeviceType, cr) + slog.Debug("adding base device type cluster requirement", slog.String("cluster", cr.ClusterName)) + } else if !conformance.IsMandatory(cr.Conformance) { + conf, confErr := cr.Conformance.Eval(cxt) + if confErr != nil { + slog.Warn("Error evaluating conformance of cluster requirement", slog.String("deviceTypeId", deviceType.ID.HexString()), slog.String("clusterName", cr.ClusterName), slog.Any("error", confErr)) + } else if conf == conformance.StateMandatory { + // If the Base Device Type has a requirement that is not plain Mandatory ("M"), but it returns Mandatory when evaulated, then include it + crr = &clusterRequirements{id: cr.ClusterID, name: cr.ClusterName} + crr.requirementsFromBaseDeviceType = append(crr.requirementsFromBaseDeviceType, cr) + clusterRequirementsByID[cr.ClusterID.Value()] = crr + } + } + } + + for _, include := range clustersElement.SelectElements("include") { + ca := include.SelectAttr("cluster") + if ca == nil { + slog.Warn("missing cluster attribute on include", slog.String("deviceTypeId", deviceType.ID.HexString())) + clustersElement.RemoveChild(include) + continue + } + var cr *clusterRequirements + var clusterId uint64 + for id, crs := range clusterRequirementsByID { + if strings.EqualFold(ca.Value, crs.name) { + cr = crs + clusterId = id + } + } + if cr == nil { + slog.Debug("unknown cluster attribute on include", slog.String("deviceTypeId", deviceType.ID.HexString()), slog.String("clusterName", ca.Value)) + clustersElement.RemoveChild(include) + continue + } + p.setIncludeAttributes(clustersElement, include, spec, deviceType, cr, cxt) + delete(clusterRequirementsByID, clusterId) + } + for _, crs := range [][]*matter.ClusterRequirement{spec.BaseDeviceType.ClusterRequirements, deviceType.ClusterRequirements} { + for _, cr := range crs { + if !cr.ClusterID.Valid() { + continue + } + crr, ok := clusterRequirementsByID[cr.ClusterID.Value()] + if ok { + p.setIncludeAttributes(clustersElement, nil, spec, deviceType, crr, cxt) + delete(clusterRequirementsByID, cr.ClusterID.Value()) + } + } + } + return +} + +func (p *DeviceTypesPatcher) setIncludeAttributes(clustersElement *etree.Element, include *etree.Element, spec *spec.Specification, deviceType *matter.DeviceType, cr *clusterRequirements, cxt conformance.Context) { + var cluster *matter.Cluster + var ok bool + cluster, ok = spec.ClustersByID[cr.id.Value()] + if !ok { + cluster, ok = spec.ClustersByName[cr.name] + if !ok { + var alias string + alias, ok = p.clusterAliases[cr.name] + if ok { + cluster, ok = spec.ClustersByName[alias] + if !ok { + slog.Warn("unknown cluster alias on include", slog.String("deviceTypeId", deviceType.ID.HexString()), slog.String("clusterName", cr.name), slog.String("alias", alias)) + return + } + } else { + slog.Warn("unknown cluster on include", slog.String("deviceTypeId", deviceType.ID.HexString()), slog.String("clusterName", cr.name)) + return + } + } + } + + clusterDoc, ok := spec.DocRefs[cluster] + if !ok { + slog.Warn("unknown doc path on include", slog.String("deviceTypeId", deviceType.ID.HexString()), slog.String("clusterName", cluster.Name)) + } + + errata := errata.GetZAP(clusterDoc.Path.Relative) + + var server, client, clientLocked, serverLocked bool + clientLocked = true + serverLocked = true + // Any requirements brought over from the Base Device Type should be overridden by device type requirements, so we parse in order + for _, crs := range [][]*matter.ClusterRequirement{cr.requirementsFromBaseDeviceType, cr.requirementsFromDeviceType} { + for _, cr := range crs { + if conformance.IsBlank(cr.Conformance) { + // If the device type has blank conformance, ignore it + continue + } + conf, err := getClusterRequirementConformance(cxt, cr) + if err != nil { + slog.Warn("Error evaluating conformance of cluster requirement", slog.String("deviceTypeId", deviceType.ID.HexString()), slog.String("clusterName", cluster.Name), slog.Any("error", err)) + continue + } + + switch conf { + case conformance.StateOptional, conformance.StateProvisional: + switch cr.Interface { + case matter.InterfaceServer: + server = false + serverLocked = false + case matter.InterfaceClient: + client = false + clientLocked = false + } + case conformance.StateMandatory: + switch cr.Interface { + case matter.InterfaceServer: + server = true + serverLocked = true + case matter.InterfaceClient: + client = true + clientLocked = true + } + case conformance.StateDisallowed: + switch cr.Interface { + case matter.InterfaceServer: + server = false + serverLocked = true + case matter.InterfaceClient: + client = false + clientLocked = true + } + default: + slog.Warn("Unexpected conformance", slog.String("deviceTypeId", deviceType.ID.HexString()), slog.String("clusterName", cluster.Name), slog.Any("conformance", conf.String())) + return + } + } + } + + if include == nil { + include = etree.NewElement("include") + include.CreateAttr("cluster", cluster.Name) + xml.InsertElementByAttribute(clustersElement, include, "cluster") + } else { + include.CreateAttr("cluster", cluster.Name) + } + + if !client && clientLocked { // Disallowed, so override whatever is there + include.CreateAttr("client", strconv.FormatBool(client)) + } else { + xml.SetNonexistentAttr(include, "client", strconv.FormatBool(client)) + } + if !server && serverLocked { // Disallowed, so override whatever is there + include.CreateAttr("server", strconv.FormatBool(server)) + } else { + xml.SetNonexistentAttr(include, "server", strconv.FormatBool(server)) + } + include.CreateAttr("clientLocked", strconv.FormatBool(clientLocked)) + include.CreateAttr("serverLocked", strconv.FormatBool(serverLocked)) + + requiredAttributes := make(map[*matter.Field]*matter.ElementRequirement) + requiredAttributeDefines := make(map[string]struct{}) + requiredCommands := make(map[*matter.Command]*matter.ElementRequirement) + requiredCommandFields := make(map[*matter.Command]map[string]*matter.ElementRequirement) + requiredEvents := make(map[*matter.Event]*matter.ElementRequirement) + requiredFeatures := make(map[*matter.Feature]*matter.ElementRequirement) + + for _, er := range cr.elementRequirements { + if conformance.IsZigbee(cluster, er.Conformance) { + continue + } + conf, err := er.Conformance.Eval(cxt) + if err != nil { + slog.Warn("Error evaluating conformance of element requirement", slog.String("deviceTypeId", deviceType.ID.HexString()), slog.String("clusterName", cluster.Name), slog.Any("error", err)) + continue + } + if conf == conformance.StateMandatory || conf == conformance.StateProvisional { + switch er.Element { + case types.EntityTypeFeature: + var feature *matter.Feature + for f := range cluster.Features.FeatureBits() { + if strings.EqualFold(er.Name, f.Name()) { + feature = f + break + } + } + if feature == nil { + slog.Warn("unknown feature in element requirement", slog.String("deviceType", deviceType.Name), slog.String("clusterName", cluster.Name), slog.String("feature", er.Name)) + continue + } + cxt.Values[er.Name] = true + requiredFeatures[feature] = er + case types.EntityTypeAttribute: + var attribute *matter.Field + for _, a := range cluster.Attributes { + if strings.EqualFold(a.Name, er.Name) { + attribute = a + break + } + } + if attribute == nil { + slog.Warn("unknown attribute in element requirement", slog.String("deviceType", deviceType.Name), slog.String("clusterName", cluster.Name), slog.String("attribute", er.Name)) + continue + } + requiredAttributes[attribute] = er + requiredAttributeDefines[getDefine(attribute.Name, errata.ClusterDefinePrefix, errata)] = struct{}{} + cxt.Values[er.Name] = true + case types.EntityTypeCommand: + var command *matter.Command + for _, cmd := range cluster.Commands { + if strings.EqualFold(cmd.Name, er.Name) { + command = cmd + break + } + } + if command == nil { + slog.Warn("unknown command in element requirement", slog.String("deviceType", deviceType.Name), slog.String("clusterName", cluster.Name), slog.String("attribute", er.Name)) + continue + } + requiredCommands[command] = er + cxt.Values[er.Name] = true + case types.EntityTypeEvent: + var event *matter.Event + for _, ev := range cluster.Events { + if strings.EqualFold(ev.Name, er.Name) { + event = ev + break + } + } + if event == nil { + slog.Warn("unknown event in element requirement", slog.String("deviceType", deviceType.Name), slog.String("clusterName", cluster.Name), slog.String("attribute", er.Name)) + continue + } + requiredEvents[event] = er + cxt.Values[er.Name] = true + case types.EntityTypeCommandField: + var command *matter.Command + for _, a := range cluster.Commands { + if strings.EqualFold(a.Name, er.Name) { + command = a + break + } + } + if command == nil { + slog.Warn("unknown command in element requirement", slog.String("deviceType", deviceType.Name), slog.String("clusterName", cluster.Name), slog.String("attribute", er.Name)) + continue + } + cf, ok := requiredCommandFields[command] + if !ok { + cf = make(map[string]*matter.ElementRequirement) + requiredCommandFields[command] = cf + cxt.Values[er.Name] = true + } + cf[er.Field] = er + default: + slog.Warn("Element requirement with unrecognized element type", slog.String("deviceType", deviceType.Name), slog.String("entityType", er.Element.String())) + } + } + } + + fse := include.SelectElement("features") + if len(requiredFeatures) > 0 { + if fse == nil { + fse = include.CreateElement("features") + } + fes := fse.SelectElements("feature") + for _, fe := range fes { + featureCode := fe.SelectAttr("code") + featureName := fe.SelectAttr("name") + if featureCode == nil && featureName == nil { + slog.Warn("feature element missing code and name attributes", slog.String("deviceType", deviceType.Name), slog.String("clusterName", cluster.Name)) + include.RemoveChild(fe) + continue + } + feature, er, required := find.FirstPairFunc(requiredFeatures, func(f *matter.Feature) bool { + if featureCode != nil && strings.EqualFold(featureCode.Value, f.Code) { + return true + } + return featureName != nil && strings.EqualFold(featureName.Value, f.Name()) + }) + if !required { + include.RemoveChild(fe) + continue + } + + fe.CreateAttr("code", feature.Code) + fe.CreateAttr("name", feature.Name()) + if p.generateFeatureXML { + renderConformance(clusterDoc, cluster, er.Conformance, fe) + } else { + removeConformance(fe) + } + delete(requiredFeatures, feature) + } + for feature, er := range requiredFeatures { + fe := etree.NewElement("feature") + fe.CreateAttr("code", feature.Code) + fe.CreateAttr("name", feature.Name()) + if p.generateFeatureXML { + renderConformance(clusterDoc, cluster, er.Conformance, fe) + } + xml.InsertElementByAttribute(fse, fe, "code") + } + } else if fse != nil { + include.RemoveChild(fse) + } + + ras := include.SelectElements("requireAttribute") + for _, ra := range ras { + rat := ra.Text() + _, required := requiredAttributeDefines[rat] + if required { + delete(requiredAttributeDefines, rat) + } else { + include.RemoveChild(ra) + } + } + for ra := range requiredAttributeDefines { + rae := etree.NewElement("requireAttribute") + rae.SetText(ra) + xml.InsertElementByName(include, rae, "") + } + rcs := include.SelectElements("requireCommand") + for _, rc := range rcs { + rct := rc.Text() + cmd, _, required := find.FirstPairFunc(requiredCommands, func(command *matter.Command) bool { return strings.EqualFold(rct, command.Name) }) + if required { + delete(requiredCommands, cmd) + } else { + include.RemoveChild(rc) + } + } + for ra := range requiredCommands { + rae := etree.NewElement("requireCommand") + rae.SetText(ra.Name) + xml.InsertElementByName(include, rae, "requireAttribute") + } + rcfs := include.SelectElements("requireCommandField") + for _, rc := range rcfs { + rcfc := rc.SelectElement("command") + if rcfc == nil { + include.RemoveChild(rc) + continue + } + rct := rcfc.Text() + cmd, fields, required := find.FirstPairFunc(requiredCommandFields, func(command *matter.Command) bool { return strings.EqualFold(rct, command.Name) }) + if required { + delete(requiredCommandFields, cmd) + } else { + include.RemoveChild(rc) + continue + } + rcffs := rc.SelectElements("field") + for _, rcff := range rcffs { + rcft := rc.Text() + _, required := fields[rcft] + if required { + delete(fields, rcft) + } else { + rc.RemoveChild(rcff) + } + } + for rcft := range fields { + rae := etree.NewElement("field") + rae.SetText(rcft) + xml.InsertElementByName(rc, rae, "command") + } + } + for rcfc, rcffs := range requiredCommandFields { + rcfe := etree.NewElement("requireCommandField") + rcfce := rcfe.CreateElement("command") + rcfce.SetText(rcfc.Name) + for rcff := range rcffs { + rcfe.CreateElement("field").SetText(rcff) + } + xml.InsertElementByName(include, rcfe, "requireAttribute", "requireCommand") + } + res := include.SelectElements("requireEvent") + for _, re := range res { + ret := re.Text() + ev, _, required := find.FirstPairFunc(requiredEvents, func(e *matter.Event) bool { return strings.EqualFold(e.Name, ret) }) + if required { + delete(requiredEvents, ev) + } else { + include.RemoveChild(re) + } + } + for ra := range requiredEvents { + rae := etree.NewElement("requireEvent") + rae.SetText(ra.Name) + xml.InsertElementByName(include, rae, "requireAttribute", "requireCommand", "requireCommandField") + } +} + +func getClusterRequirementConformance(cxt conformance.Context, cr *matter.ClusterRequirement) (conf conformance.State, err error) { + + conf, err = cr.Conformance.Eval(cxt) + if err != nil { + return + } + if conf == conformance.StateMandatory || conf == conformance.StateProvisional { + // If evaluating the conformance yields a mandatory or provisional result, take that + return + } + // Otherwise, conformances might be returning Disallowed due to conditions we're unaware of + // We'll check if the conformance is non-conditionally mandatory or disallowed, where we can be sure + if conformance.IsMandatory(cr.Conformance) { + conf = conformance.StateMandatory + return + } + if conformance.IsDisallowed(cr.Conformance) { + conf = conformance.StateDisallowed + return + } + // All other conditions will be assumed optional + conf = conformance.StateOptional + return +} diff --git a/zap/name.go b/zap/name.go index 37700e6c..1d8ef261 100644 --- a/zap/name.go +++ b/zap/name.go @@ -40,6 +40,6 @@ func ClusterName(path string, errata *errata.ZAP, entities []types.Entity) strin } func DeviceTypeName(deviceType *matter.DeviceType) string { - name := matter.Case(deviceType.Name) + name := matter.CaseWithSeparator(deviceType.Name, '-') return "MA-" + strings.ToLower(name) } diff --git a/zap/parse/attribute.go b/zap/parse/attribute.go index 8cafd5b8..39825de8 100644 --- a/zap/parse/attribute.go +++ b/zap/parse/attribute.go @@ -10,8 +10,8 @@ import ( "github.com/project-chip/alchemy/matter/types" ) -func readAttribute(d *xml.Decoder, e xml.StartElement) (attr *matter.Field, err error) { - attr = matter.NewAttribute(nil) +func readAttribute(d *xml.Decoder, e xml.StartElement, c *matter.Cluster) (attr *matter.Field, err error) { + attr = matter.NewAttribute(nil, c) attr.Access = matter.DefaultAccess(types.EntityTypeAttribute) err = readFieldAttributes(e, attr, "attribute") if err != nil { diff --git a/zap/parse/cluster.go b/zap/parse/cluster.go index 1d7574b0..4a4a6bc4 100644 --- a/zap/parse/cluster.go +++ b/zap/parse/cluster.go @@ -34,7 +34,7 @@ func readCluster(path string, d *xml.Decoder, e xml.StartElement) (cluster *matt switch t.Name.Local { case "attribute": var attribute *matter.Field - attribute, err = readAttribute(d, t) + attribute, err = readAttribute(d, t, cluster) if err == nil { cluster.Attributes = append(cluster.Attributes, attribute) } diff --git a/zap/parse/command.go b/zap/parse/command.go index 0b999f40..d48ee119 100644 --- a/zap/parse/command.go +++ b/zap/parse/command.go @@ -85,7 +85,7 @@ func readCommand(path string, d *xml.Decoder, e xml.StartElement) (c *matter.Com _, err = readSimpleElement(d, t.Name.Local) case "arg": var f *matter.Field - f, err = readField(path, d, t, types.EntityTypeCommand, "arg") + f, err = readField(path, d, t, types.EntityTypeCommand, "arg", c) if err != nil { slog.Warn("error reading command field", slog.Any("error", err)) } else { diff --git a/zap/parse/event.go b/zap/parse/event.go index 1ba660e5..1707d2c0 100644 --- a/zap/parse/event.go +++ b/zap/parse/event.go @@ -63,7 +63,7 @@ func readEvent(path string, d *xml.Decoder, e xml.StartElement) (event *matter.E event.Description, err = readSimpleElement(d, t.Name.Local) case "field": var field *matter.Field - field, err = readField(path, d, t, types.EntityTypeEvent, "field") + field, err = readField(path, d, t, types.EntityTypeEvent, "field", event) if err != nil { slog.Warn("error reading event field", slog.Any("error", err)) } else { diff --git a/zap/parse/field.go b/zap/parse/field.go index d4ade8b1..664655f2 100644 --- a/zap/parse/field.go +++ b/zap/parse/field.go @@ -13,8 +13,8 @@ import ( "github.com/project-chip/alchemy/zap" ) -func readField(path string, d *xml.Decoder, e xml.StartElement, entityType types.EntityType, name string) (field *matter.Field, err error) { - field = matter.NewField(nil) +func readField(path string, d *xml.Decoder, e xml.StartElement, entityType types.EntityType, name string, parent types.Entity) (field *matter.Field, err error) { + field = matter.NewField(nil, parent) field.Access = matter.DefaultAccess(entityType) err = readFieldAttributes(e, field, name) if err != nil { diff --git a/zap/parse/struct.go b/zap/parse/struct.go index d63f0544..4528ff8d 100644 --- a/zap/parse/struct.go +++ b/zap/parse/struct.go @@ -51,7 +51,7 @@ func (sp *ZapParser) readStruct(path string, d *xml.Decoder, e xml.StartElement) s.Description, err = readSimpleElement(d, t.Name.Local) case "item": var f *matter.Field - f, err = readField(path, d, t, types.EntityTypeStruct, "item") + f, err = readField(path, d, t, types.EntityTypeStruct, "item", s) if err != nil { slog.Warn("error reading struct field", slog.Any("error", err)) } else {