this repo has no description
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

init: working task add parser for go (#5)

Co-authored-by: Anish Lakhwara <anish+git@lakhwara.com>
Reviewed-on: https://git.sealight.xyz/aynish/mast/pulls/5

aynish b0ec8fef 176a81cc

+1810 -17
+33
README
··· 1 + # Mast 2 + 3 + This project is born out of frustration with Taskwarrior's upgrade to version 3.0.0 4 + 5 + I'm looking to build my own task management software, after nearly a decade of using taskwarrior. It tries very hard to emulate the good of taskwarrior, while also being more modern. Opinionated defaults, meets malleable software. I loved Taskwarrior, it grew with me so much, but I believe I can do better. 6 + 7 + ## Goal Map 8 + 9 + I want a syncing cross platform, fast and effective tasks program. A web client for phone usage, and a CLI client at a computer. They sync through cr-sqlite, using a fairly simple websocket server (at this stage) 10 + 11 + I plan to keep the `filter` and `add` commands from Taskwarrior. Probably lots of the properties too. I'm using PEGs for parsing these grammars. 12 + I'd like real calendar integration (iCS) for `due:` 13 + 14 + I'm not yet sure what I'd like to do for priority. I like the configurability of taskwarrior, but it's very heavy handed. Some kind of slider would be nice 15 + 16 + I also need to find out a solution to virtual tags, and especially ID 17 + 18 + I need it to keep integration with Kitaab. That largely means a _json interface, which is fine. Some compatibility layer with Taskwarrior for taskwarrior-tui would be cool too, but isn't yet on the goal list. 19 + 20 + ## Status 21 + 22 + A vaguely unordered list of the next tasks to be accomplished, and those recently completed 23 + 24 + Next: 25 + - Parsing `task add` grammar (For JS) 26 + - Make SQL files common, instead of embedded in each 27 + - Parsing `task filter` grammar 28 + - Defining all attributes 29 + - Defining virtual attributes 30 + 31 + Completed: 32 + - Syncing between JS & Go clients 33 + - Parsing `task add` grammar (For Go)
architecture.png

This is a binary file and will not be displayed.

+20 -4
cli/main.go
··· 8 8 "os" 9 9 10 10 mast "mast/db" 11 + parser "mast/parser" 11 12 12 13 "github.com/charmbracelet/bubbles/textinput" 13 14 tea "github.com/charmbracelet/bubbletea" ··· 221 222 return s 222 223 } 223 224 225 + // TODO: 226 + // Remove the parser test and call it when adding a todo 224 227 func main() { 225 - p := tea.NewProgram(initialModel()) 226 - if _, err := p.Run(); err != nil { 227 - fmt.Printf("Alas, there's been an error: %v", err) 228 - os.Exit(1) 228 + if len(os.Args) < 2 { 229 + fmt.Println("Please provide a Taskwarrior add command to parse") 230 + return 231 + } 232 + 233 + input := os.Args[1] 234 + result, err := parser.Parse("", []byte(input)) 235 + if err != nil { 236 + fmt.Printf("Error parsing input: %v\n", err) 237 + return 229 238 } 239 + 240 + fmt.Printf("Parsed result: %+v\n", result) 241 + // p := tea.NewProgram(initialModel()) 242 + // if _, err := p.Run(); err != nil { 243 + // fmt.Printf("Alas, there's been an error: %v", err) 244 + // os.Exit(1) 245 + // } 230 246 }
+4 -11
flake.nix
··· 3 3 inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 4 4 5 5 outputs = { nixpkgs, ... }: 6 - let 7 - pkgs = nixpkgs.legacyPackages."x86_64-linux"; 8 - in 9 - { 10 - devShells."x86_64-linux".default = pkgs.mkShell { 11 - packages = with pkgs; [ 12 - go 13 - sqlite 14 - nodejs 15 - ]; 6 + let pkgs = nixpkgs.legacyPackages."x86_64-linux"; 7 + in { 8 + devShells."x86_64-linux".default = 9 + pkgs.mkShell { packages = with pkgs; [ go sqlite nodejs pigeon ]; }; 16 10 }; 17 - }; 18 11 }
+5 -2
go.mod
··· 23 23 github.com/mattn/go-isatty v0.0.18 // indirect 24 24 github.com/mattn/go-localereader v0.0.1 // indirect 25 25 github.com/mattn/go-runewidth v0.0.15 // indirect 26 + github.com/mna/pigeon v1.2.1 // indirect 26 27 github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect 27 28 github.com/muesli/cancelreader v0.2.2 // indirect 28 29 github.com/muesli/reflow v0.3.0 // indirect 29 30 github.com/muesli/termenv v0.15.2 // indirect 30 31 github.com/rivo/uniseg v0.4.7 // indirect 31 32 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 32 - golang.org/x/sync v0.7.0 // indirect 33 - golang.org/x/sys v0.21.0 // indirect 33 + golang.org/x/mod v0.20.0 // indirect 34 + golang.org/x/sync v0.8.0 // indirect 35 + golang.org/x/sys v0.24.0 // indirect 34 36 golang.org/x/text v0.3.8 // indirect 37 + golang.org/x/tools v0.24.0 // indirect 35 38 )
+10
go.sum
··· 33 33 github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 34 34 github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= 35 35 github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 36 + github.com/mna/pigeon v1.2.1 h1:m5FxEbGdQxLaiHF+QurbWUAjmRqd5cstjIPN89svYgg= 37 + github.com/mna/pigeon v1.2.1/go.mod h1:BUZAoRldTdU7Ac3WYkXy8hzIHfCgj1doJxGjlB+AbLI= 36 38 github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= 37 39 github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= 38 40 github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= ··· 49 51 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 50 52 golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= 51 53 golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= 54 + golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= 55 + golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 52 56 golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= 53 57 golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 58 + golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= 59 + golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 54 60 golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 55 61 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 56 62 golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= 57 63 golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 64 + golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= 65 + golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 58 66 golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= 59 67 golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 68 + golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= 69 + golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
main

This is a binary file and will not be displayed.

+65
parser/add.peg
··· 1 + { 2 + package parser 3 + 4 + type Attribute struct{ 5 + Type string 6 + Value string 7 + } 8 + 9 + type AddCommand struct { 10 + Description string 11 + Attributes []Attribute 12 + } 13 + 14 + func MakeAdd(description interface{}, attributes interface{}) (*AddCommand, error) { 15 + attribs, _ := attributes.([]Attribute) 16 + return &AddCommand{Description: description.(string), Attributes: attribs}, nil 17 + } 18 + 19 + func MakeAttributes(attributes interface{}) ([]Attribute, error) { 20 + ats := attributes.([]interface{}) 21 + 22 + attribs := make([]Attribute, 0) 23 + for _, p := range ats { 24 + pa := p.([]interface{}) 25 + px := pa[1:] 26 + for _, pi := range px { 27 + ps := pi.(*Attribute) 28 + attribs = append(attribs, *ps) 29 + } 30 + } 31 + return attribs, nil 32 + } 33 + 34 + } 35 + 36 + Add <- desc:Description attributes:Attributes? { 37 + return MakeAdd(desc, attributes) 38 + } 39 + 40 + Description <- (!Attribute .)+ { 41 + return string(c.text), nil 42 + } 43 + 44 + Attributes <- attributes:(_ Attribute)* { 45 + return MakeAttributes(attributes) 46 + } 47 + Attribute <- Project / Priority / Tag 48 + 49 + Project <- ("pro:" / "project:") value:Word { 50 + return &Attribute{Type:"project", Value:value.(string)}, nil 51 + } 52 + Priority <- "priority:" value:[HML] { 53 + return &Attribute{Type:"priority", Value:value.(string)}, nil 54 + } 55 + 56 + Tag <- "+" value:Word { 57 + return &Attribute{Type:"tag", Value:value.(string)}, nil 58 + } 59 + 60 + Word <- [a-zA-Z0-9_-]+ { 61 + return string(c.text), nil 62 + } 63 + _ <- [ \t]* 64 + 65 + EOF = !.
+1651
parser/add_parser.go
··· 1 + // Code generated by pigeon; DO NOT EDIT. 2 + 3 + package parser 4 + 5 + import ( 6 + "bytes" 7 + "errors" 8 + "fmt" 9 + "io" 10 + "io/ioutil" 11 + "math" 12 + "os" 13 + "sort" 14 + "strconv" 15 + "strings" 16 + "sync" 17 + "unicode" 18 + "unicode/utf8" 19 + ) 20 + 21 + type Attribute struct { 22 + Type string 23 + Value string 24 + } 25 + 26 + type AddCommand struct { 27 + Description string 28 + Attributes []Attribute 29 + } 30 + 31 + func MakeAdd(description interface{}, attributes interface{}) (*AddCommand, error) { 32 + attribs, _ := attributes.([]Attribute) 33 + return &AddCommand{Description: description.(string), Attributes: attribs}, nil 34 + } 35 + 36 + func MakeAttributes(attributes interface{}) ([]Attribute, error) { 37 + ats := attributes.([]interface{}) 38 + 39 + attribs := make([]Attribute, 0) 40 + for _, p := range ats { 41 + pa := p.([]interface{}) 42 + px := pa[1:] 43 + for _, pi := range px { 44 + ps := pi.(*Attribute) 45 + attribs = append(attribs, *ps) 46 + } 47 + } 48 + return attribs, nil 49 + } 50 + 51 + var g = &grammar{ 52 + rules: []*rule{ 53 + { 54 + name: "Add", 55 + pos: position{line: 36, col: 1, offset: 688}, 56 + expr: &actionExpr{ 57 + pos: position{line: 36, col: 8, offset: 695}, 58 + run: (*parser).callonAdd1, 59 + expr: &seqExpr{ 60 + pos: position{line: 36, col: 8, offset: 695}, 61 + exprs: []interface{}{ 62 + &labeledExpr{ 63 + pos: position{line: 36, col: 8, offset: 695}, 64 + label: "desc", 65 + expr: &ruleRefExpr{ 66 + pos: position{line: 36, col: 13, offset: 700}, 67 + name: "Description", 68 + }, 69 + }, 70 + &labeledExpr{ 71 + pos: position{line: 36, col: 25, offset: 712}, 72 + label: "attributes", 73 + expr: &zeroOrOneExpr{ 74 + pos: position{line: 36, col: 36, offset: 723}, 75 + expr: &ruleRefExpr{ 76 + pos: position{line: 36, col: 36, offset: 723}, 77 + name: "Attributes", 78 + }, 79 + }, 80 + }, 81 + }, 82 + }, 83 + }, 84 + }, 85 + { 86 + name: "Description", 87 + pos: position{line: 40, col: 1, offset: 776}, 88 + expr: &actionExpr{ 89 + pos: position{line: 40, col: 16, offset: 791}, 90 + run: (*parser).callonDescription1, 91 + expr: &oneOrMoreExpr{ 92 + pos: position{line: 40, col: 16, offset: 791}, 93 + expr: &seqExpr{ 94 + pos: position{line: 40, col: 17, offset: 792}, 95 + exprs: []interface{}{ 96 + &notExpr{ 97 + pos: position{line: 40, col: 17, offset: 792}, 98 + expr: &ruleRefExpr{ 99 + pos: position{line: 40, col: 18, offset: 793}, 100 + name: "Attribute", 101 + }, 102 + }, 103 + &anyMatcher{ 104 + line: 40, col: 28, offset: 803, 105 + }, 106 + }, 107 + }, 108 + }, 109 + }, 110 + }, 111 + { 112 + name: "Attributes", 113 + pos: position{line: 44, col: 1, offset: 841}, 114 + expr: &actionExpr{ 115 + pos: position{line: 44, col: 15, offset: 855}, 116 + run: (*parser).callonAttributes1, 117 + expr: &labeledExpr{ 118 + pos: position{line: 44, col: 15, offset: 855}, 119 + label: "attributes", 120 + expr: &zeroOrMoreExpr{ 121 + pos: position{line: 44, col: 26, offset: 866}, 122 + expr: &seqExpr{ 123 + pos: position{line: 44, col: 27, offset: 867}, 124 + exprs: []interface{}{ 125 + &ruleRefExpr{ 126 + pos: position{line: 44, col: 27, offset: 867}, 127 + name: "_", 128 + }, 129 + &ruleRefExpr{ 130 + pos: position{line: 44, col: 29, offset: 869}, 131 + name: "Attribute", 132 + }, 133 + }, 134 + }, 135 + }, 136 + }, 137 + }, 138 + }, 139 + { 140 + name: "Attribute", 141 + pos: position{line: 47, col: 1, offset: 921}, 142 + expr: &choiceExpr{ 143 + pos: position{line: 47, col: 14, offset: 934}, 144 + alternatives: []interface{}{ 145 + &ruleRefExpr{ 146 + pos: position{line: 47, col: 14, offset: 934}, 147 + name: "Project", 148 + }, 149 + &ruleRefExpr{ 150 + pos: position{line: 47, col: 24, offset: 944}, 151 + name: "Priority", 152 + }, 153 + &ruleRefExpr{ 154 + pos: position{line: 47, col: 35, offset: 955}, 155 + name: "Tag", 156 + }, 157 + }, 158 + }, 159 + }, 160 + { 161 + name: "Project", 162 + pos: position{line: 49, col: 1, offset: 961}, 163 + expr: &actionExpr{ 164 + pos: position{line: 49, col: 12, offset: 972}, 165 + run: (*parser).callonProject1, 166 + expr: &seqExpr{ 167 + pos: position{line: 49, col: 12, offset: 972}, 168 + exprs: []interface{}{ 169 + &choiceExpr{ 170 + pos: position{line: 49, col: 13, offset: 973}, 171 + alternatives: []interface{}{ 172 + &litMatcher{ 173 + pos: position{line: 49, col: 13, offset: 973}, 174 + val: "pro:", 175 + ignoreCase: false, 176 + want: "\"pro:\"", 177 + }, 178 + &litMatcher{ 179 + pos: position{line: 49, col: 22, offset: 982}, 180 + val: "project:", 181 + ignoreCase: false, 182 + want: "\"project:\"", 183 + }, 184 + }, 185 + }, 186 + &labeledExpr{ 187 + pos: position{line: 49, col: 34, offset: 994}, 188 + label: "value", 189 + expr: &ruleRefExpr{ 190 + pos: position{line: 49, col: 40, offset: 1000}, 191 + name: "Word", 192 + }, 193 + }, 194 + }, 195 + }, 196 + }, 197 + }, 198 + { 199 + name: "Priority", 200 + pos: position{line: 52, col: 1, offset: 1072}, 201 + expr: &actionExpr{ 202 + pos: position{line: 52, col: 13, offset: 1084}, 203 + run: (*parser).callonPriority1, 204 + expr: &seqExpr{ 205 + pos: position{line: 52, col: 13, offset: 1084}, 206 + exprs: []interface{}{ 207 + &litMatcher{ 208 + pos: position{line: 52, col: 13, offset: 1084}, 209 + val: "priority:", 210 + ignoreCase: false, 211 + want: "\"priority:\"", 212 + }, 213 + &labeledExpr{ 214 + pos: position{line: 52, col: 25, offset: 1096}, 215 + label: "value", 216 + expr: &charClassMatcher{ 217 + pos: position{line: 52, col: 31, offset: 1102}, 218 + val: "[HML]", 219 + chars: []rune{'H', 'M', 'L'}, 220 + ignoreCase: false, 221 + inverted: false, 222 + }, 223 + }, 224 + }, 225 + }, 226 + }, 227 + }, 228 + { 229 + name: "Tag", 230 + pos: position{line: 56, col: 1, offset: 1177}, 231 + expr: &actionExpr{ 232 + pos: position{line: 56, col: 8, offset: 1184}, 233 + run: (*parser).callonTag1, 234 + expr: &seqExpr{ 235 + pos: position{line: 56, col: 8, offset: 1184}, 236 + exprs: []interface{}{ 237 + &litMatcher{ 238 + pos: position{line: 56, col: 8, offset: 1184}, 239 + val: "+", 240 + ignoreCase: false, 241 + want: "\"+\"", 242 + }, 243 + &labeledExpr{ 244 + pos: position{line: 56, col: 12, offset: 1188}, 245 + label: "value", 246 + expr: &ruleRefExpr{ 247 + pos: position{line: 56, col: 18, offset: 1194}, 248 + name: "Word", 249 + }, 250 + }, 251 + }, 252 + }, 253 + }, 254 + }, 255 + { 256 + name: "Word", 257 + pos: position{line: 60, col: 1, offset: 1263}, 258 + expr: &actionExpr{ 259 + pos: position{line: 60, col: 9, offset: 1271}, 260 + run: (*parser).callonWord1, 261 + expr: &oneOrMoreExpr{ 262 + pos: position{line: 60, col: 9, offset: 1271}, 263 + expr: &charClassMatcher{ 264 + pos: position{line: 60, col: 9, offset: 1271}, 265 + val: "[a-zA-Z0-9_-]", 266 + chars: []rune{'_', '-'}, 267 + ranges: []rune{'a', 'z', 'A', 'Z', '0', '9'}, 268 + ignoreCase: false, 269 + inverted: false, 270 + }, 271 + }, 272 + }, 273 + }, 274 + { 275 + name: "_", 276 + pos: position{line: 63, col: 1, offset: 1319}, 277 + expr: &zeroOrMoreExpr{ 278 + pos: position{line: 63, col: 6, offset: 1324}, 279 + expr: &charClassMatcher{ 280 + pos: position{line: 63, col: 6, offset: 1324}, 281 + val: "[ \\t]", 282 + chars: []rune{' ', '\t'}, 283 + ignoreCase: false, 284 + inverted: false, 285 + }, 286 + }, 287 + }, 288 + { 289 + name: "EOF", 290 + pos: position{line: 65, col: 1, offset: 1332}, 291 + expr: &notExpr{ 292 + pos: position{line: 65, col: 7, offset: 1338}, 293 + expr: &anyMatcher{ 294 + line: 65, col: 8, offset: 1339, 295 + }, 296 + }, 297 + }, 298 + }, 299 + } 300 + 301 + func (c *current) onAdd1(desc, attributes interface{}) (interface{}, error) { 302 + return MakeAdd(desc, attributes) 303 + } 304 + 305 + func (p *parser) callonAdd1() (interface{}, error) { 306 + stack := p.vstack[len(p.vstack)-1] 307 + _ = stack 308 + return p.cur.onAdd1(stack["desc"], stack["attributes"]) 309 + } 310 + 311 + func (c *current) onDescription1() (interface{}, error) { 312 + return string(c.text), nil 313 + } 314 + 315 + func (p *parser) callonDescription1() (interface{}, error) { 316 + stack := p.vstack[len(p.vstack)-1] 317 + _ = stack 318 + return p.cur.onDescription1() 319 + } 320 + 321 + func (c *current) onAttributes1(attributes interface{}) (interface{}, error) { 322 + return MakeAttributes(attributes) 323 + } 324 + 325 + func (p *parser) callonAttributes1() (interface{}, error) { 326 + stack := p.vstack[len(p.vstack)-1] 327 + _ = stack 328 + return p.cur.onAttributes1(stack["attributes"]) 329 + } 330 + 331 + func (c *current) onProject1(value interface{}) (interface{}, error) { 332 + return &Attribute{Type: "project", Value: value.(string)}, nil 333 + } 334 + 335 + func (p *parser) callonProject1() (interface{}, error) { 336 + stack := p.vstack[len(p.vstack)-1] 337 + _ = stack 338 + return p.cur.onProject1(stack["value"]) 339 + } 340 + 341 + func (c *current) onPriority1(value interface{}) (interface{}, error) { 342 + return &Attribute{Type: "priority", Value: value.(string)}, nil 343 + } 344 + 345 + func (p *parser) callonPriority1() (interface{}, error) { 346 + stack := p.vstack[len(p.vstack)-1] 347 + _ = stack 348 + return p.cur.onPriority1(stack["value"]) 349 + } 350 + 351 + func (c *current) onTag1(value interface{}) (interface{}, error) { 352 + return &Attribute{Type: "tag", Value: value.(string)}, nil 353 + } 354 + 355 + func (p *parser) callonTag1() (interface{}, error) { 356 + stack := p.vstack[len(p.vstack)-1] 357 + _ = stack 358 + return p.cur.onTag1(stack["value"]) 359 + } 360 + 361 + func (c *current) onWord1() (interface{}, error) { 362 + return string(c.text), nil 363 + } 364 + 365 + func (p *parser) callonWord1() (interface{}, error) { 366 + stack := p.vstack[len(p.vstack)-1] 367 + _ = stack 368 + return p.cur.onWord1() 369 + } 370 + 371 + var ( 372 + // errNoRule is returned when the grammar to parse has no rule. 373 + errNoRule = errors.New("grammar has no rule") 374 + 375 + // errInvalidEntrypoint is returned when the specified entrypoint rule 376 + // does not exit. 377 + errInvalidEntrypoint = errors.New("invalid entrypoint") 378 + 379 + // errInvalidEncoding is returned when the source is not properly 380 + // utf8-encoded. 381 + errInvalidEncoding = errors.New("invalid encoding") 382 + 383 + // errMaxExprCnt is used to signal that the maximum number of 384 + // expressions have been parsed. 385 + errMaxExprCnt = errors.New("max number of expresssions parsed") 386 + ) 387 + 388 + // Option is a function that can set an option on the parser. It returns 389 + // the previous setting as an Option. 390 + type Option func(*parser) Option 391 + 392 + // MaxExpressions creates an Option to stop parsing after the provided 393 + // number of expressions have been parsed, if the value is 0 then the parser will 394 + // parse for as many steps as needed (possibly an infinite number). 395 + // 396 + // The default for maxExprCnt is 0. 397 + func MaxExpressions(maxExprCnt uint64) Option { 398 + return func(p *parser) Option { 399 + oldMaxExprCnt := p.maxExprCnt 400 + p.maxExprCnt = maxExprCnt 401 + return MaxExpressions(oldMaxExprCnt) 402 + } 403 + } 404 + 405 + // Entrypoint creates an Option to set the rule name to use as entrypoint. 406 + // The rule name must have been specified in the -alternate-entrypoints 407 + // if generating the parser with the -optimize-grammar flag, otherwise 408 + // it may have been optimized out. Passing an empty string sets the 409 + // entrypoint to the first rule in the grammar. 410 + // 411 + // The default is to start parsing at the first rule in the grammar. 412 + func Entrypoint(ruleName string) Option { 413 + return func(p *parser) Option { 414 + oldEntrypoint := p.entrypoint 415 + p.entrypoint = ruleName 416 + if ruleName == "" { 417 + p.entrypoint = g.rules[0].name 418 + } 419 + return Entrypoint(oldEntrypoint) 420 + } 421 + } 422 + 423 + // Statistics adds a user provided Stats struct to the parser to allow 424 + // the user to process the results after the parsing has finished. 425 + // Also the key for the "no match" counter is set. 426 + // 427 + // Example usage: 428 + // 429 + // input := "input" 430 + // stats := Stats{} 431 + // _, err := Parse("input-file", []byte(input), Statistics(&stats, "no match")) 432 + // if err != nil { 433 + // log.Panicln(err) 434 + // } 435 + // b, err := json.MarshalIndent(stats.ChoiceAltCnt, "", " ") 436 + // if err != nil { 437 + // log.Panicln(err) 438 + // } 439 + // fmt.Println(string(b)) 440 + func Statistics(stats *Stats, choiceNoMatch string) Option { 441 + return func(p *parser) Option { 442 + oldStats := p.Stats 443 + p.Stats = stats 444 + oldChoiceNoMatch := p.choiceNoMatch 445 + p.choiceNoMatch = choiceNoMatch 446 + if p.Stats.ChoiceAltCnt == nil { 447 + p.Stats.ChoiceAltCnt = make(map[string]map[string]int) 448 + } 449 + return Statistics(oldStats, oldChoiceNoMatch) 450 + } 451 + } 452 + 453 + // Debug creates an Option to set the debug flag to b. When set to true, 454 + // debugging information is printed to stdout while parsing. 455 + // 456 + // The default is false. 457 + func Debug(b bool) Option { 458 + return func(p *parser) Option { 459 + old := p.debug 460 + p.debug = b 461 + return Debug(old) 462 + } 463 + } 464 + 465 + // Memoize creates an Option to set the memoize flag to b. When set to true, 466 + // the parser will cache all results so each expression is evaluated only 467 + // once. This guarantees linear parsing time even for pathological cases, 468 + // at the expense of more memory and slower times for typical cases. 469 + // 470 + // The default is false. 471 + func Memoize(b bool) Option { 472 + return func(p *parser) Option { 473 + old := p.memoize 474 + p.memoize = b 475 + return Memoize(old) 476 + } 477 + } 478 + 479 + // AllowInvalidUTF8 creates an Option to allow invalid UTF-8 bytes. 480 + // Every invalid UTF-8 byte is treated as a utf8.RuneError (U+FFFD) 481 + // by character class matchers and is matched by the any matcher. 482 + // The returned matched value, c.text and c.offset are NOT affected. 483 + // 484 + // The default is false. 485 + func AllowInvalidUTF8(b bool) Option { 486 + return func(p *parser) Option { 487 + old := p.allowInvalidUTF8 488 + p.allowInvalidUTF8 = b 489 + return AllowInvalidUTF8(old) 490 + } 491 + } 492 + 493 + // Recover creates an Option to set the recover flag to b. When set to 494 + // true, this causes the parser to recover from panics and convert it 495 + // to an error. Setting it to false can be useful while debugging to 496 + // access the full stack trace. 497 + // 498 + // The default is true. 499 + func Recover(b bool) Option { 500 + return func(p *parser) Option { 501 + old := p.recover 502 + p.recover = b 503 + return Recover(old) 504 + } 505 + } 506 + 507 + // GlobalStore creates an Option to set a key to a certain value in 508 + // the globalStore. 509 + func GlobalStore(key string, value interface{}) Option { 510 + return func(p *parser) Option { 511 + old := p.cur.globalStore[key] 512 + p.cur.globalStore[key] = value 513 + return GlobalStore(key, old) 514 + } 515 + } 516 + 517 + // InitState creates an Option to set a key to a certain value in 518 + // the global "state" store. 519 + func InitState(key string, value interface{}) Option { 520 + return func(p *parser) Option { 521 + old := p.cur.state[key] 522 + p.cur.state[key] = value 523 + return InitState(key, old) 524 + } 525 + } 526 + 527 + // ParseFile parses the file identified by filename. 528 + func ParseFile(filename string, opts ...Option) (i interface{}, err error) { 529 + f, err := os.Open(filename) 530 + if err != nil { 531 + return nil, err 532 + } 533 + defer func() { 534 + if closeErr := f.Close(); closeErr != nil { 535 + err = closeErr 536 + } 537 + }() 538 + return ParseReader(filename, f, opts...) 539 + } 540 + 541 + // ParseReader parses the data from r using filename as information in the 542 + // error messages. 543 + func ParseReader(filename string, r io.Reader, opts ...Option) (interface{}, error) { 544 + b, err := ioutil.ReadAll(r) 545 + if err != nil { 546 + return nil, err 547 + } 548 + 549 + return Parse(filename, b, opts...) 550 + } 551 + 552 + // Parse parses the data from b using filename as information in the 553 + // error messages. 554 + func Parse(filename string, b []byte, opts ...Option) (interface{}, error) { 555 + return newParser(filename, b, opts...).parse(g) 556 + } 557 + 558 + // position records a position in the text. 559 + type position struct { 560 + line, col, offset int 561 + } 562 + 563 + func (p position) String() string { 564 + return strconv.Itoa(p.line) + ":" + strconv.Itoa(p.col) + " [" + strconv.Itoa(p.offset) + "]" 565 + } 566 + 567 + // savepoint stores all state required to go back to this point in the 568 + // parser. 569 + type savepoint struct { 570 + position 571 + rn rune 572 + w int 573 + } 574 + 575 + type current struct { 576 + pos position // start position of the match 577 + text []byte // raw text of the match 578 + 579 + // state is a store for arbitrary key,value pairs that the user wants to be 580 + // tied to the backtracking of the parser. 581 + // This is always rolled back if a parsing rule fails. 582 + state storeDict 583 + 584 + // globalStore is a general store for the user to store arbitrary key-value 585 + // pairs that they need to manage and that they do not want tied to the 586 + // backtracking of the parser. This is only modified by the user and never 587 + // rolled back by the parser. It is always up to the user to keep this in a 588 + // consistent state. 589 + globalStore storeDict 590 + } 591 + 592 + type storeDict map[string]interface{} 593 + 594 + // the AST types... 595 + 596 + type grammar struct { 597 + pos position 598 + rules []*rule 599 + } 600 + 601 + type rule struct { 602 + pos position 603 + name string 604 + displayName string 605 + expr interface{} 606 + } 607 + 608 + type choiceExpr struct { 609 + pos position 610 + alternatives []interface{} 611 + } 612 + 613 + type actionExpr struct { 614 + pos position 615 + expr interface{} 616 + run func(*parser) (interface{}, error) 617 + } 618 + 619 + type recoveryExpr struct { 620 + pos position 621 + expr interface{} 622 + recoverExpr interface{} 623 + failureLabel []string 624 + } 625 + 626 + type seqExpr struct { 627 + pos position 628 + exprs []interface{} 629 + } 630 + 631 + type throwExpr struct { 632 + pos position 633 + label string 634 + } 635 + 636 + type labeledExpr struct { 637 + pos position 638 + label string 639 + expr interface{} 640 + } 641 + 642 + type expr struct { 643 + pos position 644 + expr interface{} 645 + } 646 + 647 + type andExpr expr 648 + type notExpr expr 649 + type zeroOrOneExpr expr 650 + type zeroOrMoreExpr expr 651 + type oneOrMoreExpr expr 652 + 653 + type ruleRefExpr struct { 654 + pos position 655 + name string 656 + } 657 + 658 + type stateCodeExpr struct { 659 + pos position 660 + run func(*parser) error 661 + } 662 + 663 + type andCodeExpr struct { 664 + pos position 665 + run func(*parser) (bool, error) 666 + } 667 + 668 + type notCodeExpr struct { 669 + pos position 670 + run func(*parser) (bool, error) 671 + } 672 + 673 + type litMatcher struct { 674 + pos position 675 + val string 676 + ignoreCase bool 677 + want string 678 + } 679 + 680 + type charClassMatcher struct { 681 + pos position 682 + val string 683 + basicLatinChars [128]bool 684 + chars []rune 685 + ranges []rune 686 + classes []*unicode.RangeTable 687 + ignoreCase bool 688 + inverted bool 689 + } 690 + 691 + type anyMatcher position 692 + 693 + // errList cumulates the errors found by the parser. 694 + type errList []error 695 + 696 + func (e *errList) add(err error) { 697 + *e = append(*e, err) 698 + } 699 + 700 + func (e errList) err() error { 701 + if len(e) == 0 { 702 + return nil 703 + } 704 + e.dedupe() 705 + return e 706 + } 707 + 708 + func (e *errList) dedupe() { 709 + var cleaned []error 710 + set := make(map[string]bool) 711 + for _, err := range *e { 712 + if msg := err.Error(); !set[msg] { 713 + set[msg] = true 714 + cleaned = append(cleaned, err) 715 + } 716 + } 717 + *e = cleaned 718 + } 719 + 720 + func (e errList) Error() string { 721 + switch len(e) { 722 + case 0: 723 + return "" 724 + case 1: 725 + return e[0].Error() 726 + default: 727 + var buf bytes.Buffer 728 + 729 + for i, err := range e { 730 + if i > 0 { 731 + buf.WriteRune('\n') 732 + } 733 + buf.WriteString(err.Error()) 734 + } 735 + return buf.String() 736 + } 737 + } 738 + 739 + // parserError wraps an error with a prefix indicating the rule in which 740 + // the error occurred. The original error is stored in the Inner field. 741 + type parserError struct { 742 + Inner error 743 + pos position 744 + prefix string 745 + expected []string 746 + } 747 + 748 + // Error returns the error message. 749 + func (p *parserError) Error() string { 750 + return p.prefix + ": " + p.Inner.Error() 751 + } 752 + 753 + // newParser creates a parser with the specified input source and options. 754 + func newParser(filename string, b []byte, opts ...Option) *parser { 755 + stats := Stats{ 756 + ChoiceAltCnt: make(map[string]map[string]int), 757 + } 758 + 759 + p := &parser{ 760 + filename: filename, 761 + errs: new(errList), 762 + data: b, 763 + pt: savepoint{position: position{line: 1}}, 764 + recover: true, 765 + cur: current{ 766 + state: make(storeDict), 767 + globalStore: make(storeDict), 768 + }, 769 + maxFailPos: position{col: 1, line: 1}, 770 + maxFailExpected: make([]string, 0, 20), 771 + Stats: &stats, 772 + // start rule is rule [0] unless an alternate entrypoint is specified 773 + entrypoint: g.rules[0].name, 774 + } 775 + p.setOptions(opts) 776 + 777 + if p.maxExprCnt == 0 { 778 + p.maxExprCnt = math.MaxUint64 779 + } 780 + 781 + return p 782 + } 783 + 784 + // setOptions applies the options to the parser. 785 + func (p *parser) setOptions(opts []Option) { 786 + for _, opt := range opts { 787 + opt(p) 788 + } 789 + } 790 + 791 + type resultTuple struct { 792 + v interface{} 793 + b bool 794 + end savepoint 795 + } 796 + 797 + const choiceNoMatch = -1 798 + 799 + // Stats stores some statistics, gathered during parsing 800 + type Stats struct { 801 + // ExprCnt counts the number of expressions processed during parsing 802 + // This value is compared to the maximum number of expressions allowed 803 + // (set by the MaxExpressions option). 804 + ExprCnt uint64 805 + 806 + // ChoiceAltCnt is used to count for each ordered choice expression, 807 + // which alternative is used how may times. 808 + // These numbers allow to optimize the order of the ordered choice expression 809 + // to increase the performance of the parser 810 + // 811 + // The outer key of ChoiceAltCnt is composed of the name of the rule as well 812 + // as the line and the column of the ordered choice. 813 + // The inner key of ChoiceAltCnt is the number (one-based) of the matching alternative. 814 + // For each alternative the number of matches are counted. If an ordered choice does not 815 + // match, a special counter is incremented. The name of this counter is set with 816 + // the parser option Statistics. 817 + // For an alternative to be included in ChoiceAltCnt, it has to match at least once. 818 + ChoiceAltCnt map[string]map[string]int 819 + } 820 + 821 + type parser struct { 822 + filename string 823 + pt savepoint 824 + cur current 825 + 826 + data []byte 827 + errs *errList 828 + 829 + depth int 830 + recover bool 831 + debug bool 832 + 833 + memoize bool 834 + // memoization table for the packrat algorithm: 835 + // map[offset in source] map[expression or rule] {value, match} 836 + memo map[int]map[interface{}]resultTuple 837 + 838 + // rules table, maps the rule identifier to the rule node 839 + rules map[string]*rule 840 + // variables stack, map of label to value 841 + vstack []map[string]interface{} 842 + // rule stack, allows identification of the current rule in errors 843 + rstack []*rule 844 + 845 + // parse fail 846 + maxFailPos position 847 + maxFailExpected []string 848 + maxFailInvertExpected bool 849 + 850 + // max number of expressions to be parsed 851 + maxExprCnt uint64 852 + // entrypoint for the parser 853 + entrypoint string 854 + 855 + allowInvalidUTF8 bool 856 + 857 + *Stats 858 + 859 + choiceNoMatch string 860 + // recovery expression stack, keeps track of the currently available recovery expression, these are traversed in reverse 861 + recoveryStack []map[string]interface{} 862 + } 863 + 864 + // push a variable set on the vstack. 865 + func (p *parser) pushV() { 866 + if cap(p.vstack) == len(p.vstack) { 867 + // create new empty slot in the stack 868 + p.vstack = append(p.vstack, nil) 869 + } else { 870 + // slice to 1 more 871 + p.vstack = p.vstack[:len(p.vstack)+1] 872 + } 873 + 874 + // get the last args set 875 + m := p.vstack[len(p.vstack)-1] 876 + if m != nil && len(m) == 0 { 877 + // empty map, all good 878 + return 879 + } 880 + 881 + m = make(map[string]interface{}) 882 + p.vstack[len(p.vstack)-1] = m 883 + } 884 + 885 + // pop a variable set from the vstack. 886 + func (p *parser) popV() { 887 + // if the map is not empty, clear it 888 + m := p.vstack[len(p.vstack)-1] 889 + if len(m) > 0 { 890 + // GC that map 891 + p.vstack[len(p.vstack)-1] = nil 892 + } 893 + p.vstack = p.vstack[:len(p.vstack)-1] 894 + } 895 + 896 + // push a recovery expression with its labels to the recoveryStack 897 + func (p *parser) pushRecovery(labels []string, expr interface{}) { 898 + if cap(p.recoveryStack) == len(p.recoveryStack) { 899 + // create new empty slot in the stack 900 + p.recoveryStack = append(p.recoveryStack, nil) 901 + } else { 902 + // slice to 1 more 903 + p.recoveryStack = p.recoveryStack[:len(p.recoveryStack)+1] 904 + } 905 + 906 + m := make(map[string]interface{}, len(labels)) 907 + for _, fl := range labels { 908 + m[fl] = expr 909 + } 910 + p.recoveryStack[len(p.recoveryStack)-1] = m 911 + } 912 + 913 + // pop a recovery expression from the recoveryStack 914 + func (p *parser) popRecovery() { 915 + // GC that map 916 + p.recoveryStack[len(p.recoveryStack)-1] = nil 917 + 918 + p.recoveryStack = p.recoveryStack[:len(p.recoveryStack)-1] 919 + } 920 + 921 + func (p *parser) print(prefix, s string) string { 922 + if !p.debug { 923 + return s 924 + } 925 + 926 + fmt.Printf("%s %d:%d:%d: %s [%#U]\n", 927 + prefix, p.pt.line, p.pt.col, p.pt.offset, s, p.pt.rn) 928 + return s 929 + } 930 + 931 + func (p *parser) in(s string) string { 932 + p.depth++ 933 + return p.print(strings.Repeat(" ", p.depth)+">", s) 934 + } 935 + 936 + func (p *parser) out(s string) string { 937 + p.depth-- 938 + return p.print(strings.Repeat(" ", p.depth)+"<", s) 939 + } 940 + 941 + func (p *parser) addErr(err error) { 942 + p.addErrAt(err, p.pt.position, []string{}) 943 + } 944 + 945 + func (p *parser) addErrAt(err error, pos position, expected []string) { 946 + var buf bytes.Buffer 947 + if p.filename != "" { 948 + buf.WriteString(p.filename) 949 + } 950 + if buf.Len() > 0 { 951 + buf.WriteString(":") 952 + } 953 + buf.WriteString(fmt.Sprintf("%d:%d (%d)", pos.line, pos.col, pos.offset)) 954 + if len(p.rstack) > 0 { 955 + if buf.Len() > 0 { 956 + buf.WriteString(": ") 957 + } 958 + rule := p.rstack[len(p.rstack)-1] 959 + if rule.displayName != "" { 960 + buf.WriteString("rule " + rule.displayName) 961 + } else { 962 + buf.WriteString("rule " + rule.name) 963 + } 964 + } 965 + pe := &parserError{Inner: err, pos: pos, prefix: buf.String(), expected: expected} 966 + p.errs.add(pe) 967 + } 968 + 969 + func (p *parser) failAt(fail bool, pos position, want string) { 970 + // process fail if parsing fails and not inverted or parsing succeeds and invert is set 971 + if fail == p.maxFailInvertExpected { 972 + if pos.offset < p.maxFailPos.offset { 973 + return 974 + } 975 + 976 + if pos.offset > p.maxFailPos.offset { 977 + p.maxFailPos = pos 978 + p.maxFailExpected = p.maxFailExpected[:0] 979 + } 980 + 981 + if p.maxFailInvertExpected { 982 + want = "!" + want 983 + } 984 + p.maxFailExpected = append(p.maxFailExpected, want) 985 + } 986 + } 987 + 988 + // read advances the parser to the next rune. 989 + func (p *parser) read() { 990 + p.pt.offset += p.pt.w 991 + rn, n := utf8.DecodeRune(p.data[p.pt.offset:]) 992 + p.pt.rn = rn 993 + p.pt.w = n 994 + p.pt.col++ 995 + if rn == '\n' { 996 + p.pt.line++ 997 + p.pt.col = 0 998 + } 999 + 1000 + if rn == utf8.RuneError && n == 1 { // see utf8.DecodeRune 1001 + if !p.allowInvalidUTF8 { 1002 + p.addErr(errInvalidEncoding) 1003 + } 1004 + } 1005 + } 1006 + 1007 + // restore parser position to the savepoint pt. 1008 + func (p *parser) restore(pt savepoint) { 1009 + if p.debug { 1010 + defer p.out(p.in("restore")) 1011 + } 1012 + if pt.offset == p.pt.offset { 1013 + return 1014 + } 1015 + p.pt = pt 1016 + } 1017 + 1018 + // Cloner is implemented by any value that has a Clone method, which returns a 1019 + // copy of the value. This is mainly used for types which are not passed by 1020 + // value (e.g map, slice, chan) or structs that contain such types. 1021 + // 1022 + // This is used in conjunction with the global state feature to create proper 1023 + // copies of the state to allow the parser to properly restore the state in 1024 + // the case of backtracking. 1025 + type Cloner interface { 1026 + Clone() interface{} 1027 + } 1028 + 1029 + var statePool = &sync.Pool{ 1030 + New: func() interface{} { return make(storeDict) }, 1031 + } 1032 + 1033 + func (sd storeDict) Discard() { 1034 + for k := range sd { 1035 + delete(sd, k) 1036 + } 1037 + statePool.Put(sd) 1038 + } 1039 + 1040 + // clone and return parser current state. 1041 + func (p *parser) cloneState() storeDict { 1042 + if p.debug { 1043 + defer p.out(p.in("cloneState")) 1044 + } 1045 + 1046 + state := statePool.Get().(storeDict) 1047 + for k, v := range p.cur.state { 1048 + if c, ok := v.(Cloner); ok { 1049 + state[k] = c.Clone() 1050 + } else { 1051 + state[k] = v 1052 + } 1053 + } 1054 + return state 1055 + } 1056 + 1057 + // restore parser current state to the state storeDict. 1058 + // every restoreState should applied only one time for every cloned state 1059 + func (p *parser) restoreState(state storeDict) { 1060 + if p.debug { 1061 + defer p.out(p.in("restoreState")) 1062 + } 1063 + p.cur.state.Discard() 1064 + p.cur.state = state 1065 + } 1066 + 1067 + // get the slice of bytes from the savepoint start to the current position. 1068 + func (p *parser) sliceFrom(start savepoint) []byte { 1069 + return p.data[start.position.offset:p.pt.position.offset] 1070 + } 1071 + 1072 + func (p *parser) getMemoized(node interface{}) (resultTuple, bool) { 1073 + if len(p.memo) == 0 { 1074 + return resultTuple{}, false 1075 + } 1076 + m := p.memo[p.pt.offset] 1077 + if len(m) == 0 { 1078 + return resultTuple{}, false 1079 + } 1080 + res, ok := m[node] 1081 + return res, ok 1082 + } 1083 + 1084 + func (p *parser) setMemoized(pt savepoint, node interface{}, tuple resultTuple) { 1085 + if p.memo == nil { 1086 + p.memo = make(map[int]map[interface{}]resultTuple) 1087 + } 1088 + m := p.memo[pt.offset] 1089 + if m == nil { 1090 + m = make(map[interface{}]resultTuple) 1091 + p.memo[pt.offset] = m 1092 + } 1093 + m[node] = tuple 1094 + } 1095 + 1096 + func (p *parser) buildRulesTable(g *grammar) { 1097 + p.rules = make(map[string]*rule, len(g.rules)) 1098 + for _, r := range g.rules { 1099 + p.rules[r.name] = r 1100 + } 1101 + } 1102 + 1103 + func (p *parser) parse(g *grammar) (val interface{}, err error) { 1104 + if len(g.rules) == 0 { 1105 + p.addErr(errNoRule) 1106 + return nil, p.errs.err() 1107 + } 1108 + 1109 + // TODO : not super critical but this could be generated 1110 + p.buildRulesTable(g) 1111 + 1112 + if p.recover { 1113 + // panic can be used in action code to stop parsing immediately 1114 + // and return the panic as an error. 1115 + defer func() { 1116 + if e := recover(); e != nil { 1117 + if p.debug { 1118 + defer p.out(p.in("panic handler")) 1119 + } 1120 + val = nil 1121 + switch e := e.(type) { 1122 + case error: 1123 + p.addErr(e) 1124 + default: 1125 + p.addErr(fmt.Errorf("%v", e)) 1126 + } 1127 + err = p.errs.err() 1128 + } 1129 + }() 1130 + } 1131 + 1132 + startRule, ok := p.rules[p.entrypoint] 1133 + if !ok { 1134 + p.addErr(errInvalidEntrypoint) 1135 + return nil, p.errs.err() 1136 + } 1137 + 1138 + p.read() // advance to first rune 1139 + val, ok = p.parseRule(startRule) 1140 + if !ok { 1141 + if len(*p.errs) == 0 { 1142 + // If parsing fails, but no errors have been recorded, the expected values 1143 + // for the farthest parser position are returned as error. 1144 + maxFailExpectedMap := make(map[string]struct{}, len(p.maxFailExpected)) 1145 + for _, v := range p.maxFailExpected { 1146 + maxFailExpectedMap[v] = struct{}{} 1147 + } 1148 + expected := make([]string, 0, len(maxFailExpectedMap)) 1149 + eof := false 1150 + if _, ok := maxFailExpectedMap["!."]; ok { 1151 + delete(maxFailExpectedMap, "!.") 1152 + eof = true 1153 + } 1154 + for k := range maxFailExpectedMap { 1155 + expected = append(expected, k) 1156 + } 1157 + sort.Strings(expected) 1158 + if eof { 1159 + expected = append(expected, "EOF") 1160 + } 1161 + p.addErrAt(errors.New("no match found, expected: "+listJoin(expected, ", ", "or")), p.maxFailPos, expected) 1162 + } 1163 + 1164 + return nil, p.errs.err() 1165 + } 1166 + return val, p.errs.err() 1167 + } 1168 + 1169 + func listJoin(list []string, sep string, lastSep string) string { 1170 + switch len(list) { 1171 + case 0: 1172 + return "" 1173 + case 1: 1174 + return list[0] 1175 + default: 1176 + return strings.Join(list[:len(list)-1], sep) + " " + lastSep + " " + list[len(list)-1] 1177 + } 1178 + } 1179 + 1180 + func (p *parser) parseRule(rule *rule) (interface{}, bool) { 1181 + if p.debug { 1182 + defer p.out(p.in("parseRule " + rule.name)) 1183 + } 1184 + 1185 + if p.memoize { 1186 + res, ok := p.getMemoized(rule) 1187 + if ok { 1188 + p.restore(res.end) 1189 + return res.v, res.b 1190 + } 1191 + } 1192 + 1193 + start := p.pt 1194 + p.rstack = append(p.rstack, rule) 1195 + p.pushV() 1196 + val, ok := p.parseExpr(rule.expr) 1197 + p.popV() 1198 + p.rstack = p.rstack[:len(p.rstack)-1] 1199 + if ok && p.debug { 1200 + p.print(strings.Repeat(" ", p.depth)+"MATCH", string(p.sliceFrom(start))) 1201 + } 1202 + 1203 + if p.memoize { 1204 + p.setMemoized(start, rule, resultTuple{val, ok, p.pt}) 1205 + } 1206 + return val, ok 1207 + } 1208 + 1209 + func (p *parser) parseExpr(expr interface{}) (interface{}, bool) { 1210 + var pt savepoint 1211 + 1212 + if p.memoize { 1213 + res, ok := p.getMemoized(expr) 1214 + if ok { 1215 + p.restore(res.end) 1216 + return res.v, res.b 1217 + } 1218 + pt = p.pt 1219 + } 1220 + 1221 + p.ExprCnt++ 1222 + if p.ExprCnt > p.maxExprCnt { 1223 + panic(errMaxExprCnt) 1224 + } 1225 + 1226 + var val interface{} 1227 + var ok bool 1228 + switch expr := expr.(type) { 1229 + case *actionExpr: 1230 + val, ok = p.parseActionExpr(expr) 1231 + case *andCodeExpr: 1232 + val, ok = p.parseAndCodeExpr(expr) 1233 + case *andExpr: 1234 + val, ok = p.parseAndExpr(expr) 1235 + case *anyMatcher: 1236 + val, ok = p.parseAnyMatcher(expr) 1237 + case *charClassMatcher: 1238 + val, ok = p.parseCharClassMatcher(expr) 1239 + case *choiceExpr: 1240 + val, ok = p.parseChoiceExpr(expr) 1241 + case *labeledExpr: 1242 + val, ok = p.parseLabeledExpr(expr) 1243 + case *litMatcher: 1244 + val, ok = p.parseLitMatcher(expr) 1245 + case *notCodeExpr: 1246 + val, ok = p.parseNotCodeExpr(expr) 1247 + case *notExpr: 1248 + val, ok = p.parseNotExpr(expr) 1249 + case *oneOrMoreExpr: 1250 + val, ok = p.parseOneOrMoreExpr(expr) 1251 + case *recoveryExpr: 1252 + val, ok = p.parseRecoveryExpr(expr) 1253 + case *ruleRefExpr: 1254 + val, ok = p.parseRuleRefExpr(expr) 1255 + case *seqExpr: 1256 + val, ok = p.parseSeqExpr(expr) 1257 + case *stateCodeExpr: 1258 + val, ok = p.parseStateCodeExpr(expr) 1259 + case *throwExpr: 1260 + val, ok = p.parseThrowExpr(expr) 1261 + case *zeroOrMoreExpr: 1262 + val, ok = p.parseZeroOrMoreExpr(expr) 1263 + case *zeroOrOneExpr: 1264 + val, ok = p.parseZeroOrOneExpr(expr) 1265 + default: 1266 + panic(fmt.Sprintf("unknown expression type %T", expr)) 1267 + } 1268 + if p.memoize { 1269 + p.setMemoized(pt, expr, resultTuple{val, ok, p.pt}) 1270 + } 1271 + return val, ok 1272 + } 1273 + 1274 + func (p *parser) parseActionExpr(act *actionExpr) (interface{}, bool) { 1275 + if p.debug { 1276 + defer p.out(p.in("parseActionExpr")) 1277 + } 1278 + 1279 + start := p.pt 1280 + val, ok := p.parseExpr(act.expr) 1281 + if ok { 1282 + p.cur.pos = start.position 1283 + p.cur.text = p.sliceFrom(start) 1284 + state := p.cloneState() 1285 + actVal, err := act.run(p) 1286 + if err != nil { 1287 + p.addErrAt(err, start.position, []string{}) 1288 + } 1289 + p.restoreState(state) 1290 + 1291 + val = actVal 1292 + } 1293 + if ok && p.debug { 1294 + p.print(strings.Repeat(" ", p.depth)+"MATCH", string(p.sliceFrom(start))) 1295 + } 1296 + return val, ok 1297 + } 1298 + 1299 + func (p *parser) parseAndCodeExpr(and *andCodeExpr) (interface{}, bool) { 1300 + if p.debug { 1301 + defer p.out(p.in("parseAndCodeExpr")) 1302 + } 1303 + 1304 + state := p.cloneState() 1305 + 1306 + ok, err := and.run(p) 1307 + if err != nil { 1308 + p.addErr(err) 1309 + } 1310 + p.restoreState(state) 1311 + 1312 + return nil, ok 1313 + } 1314 + 1315 + func (p *parser) parseAndExpr(and *andExpr) (interface{}, bool) { 1316 + if p.debug { 1317 + defer p.out(p.in("parseAndExpr")) 1318 + } 1319 + 1320 + pt := p.pt 1321 + state := p.cloneState() 1322 + p.pushV() 1323 + _, ok := p.parseExpr(and.expr) 1324 + p.popV() 1325 + p.restoreState(state) 1326 + p.restore(pt) 1327 + 1328 + return nil, ok 1329 + } 1330 + 1331 + func (p *parser) parseAnyMatcher(any *anyMatcher) (interface{}, bool) { 1332 + if p.debug { 1333 + defer p.out(p.in("parseAnyMatcher")) 1334 + } 1335 + 1336 + if p.pt.rn == utf8.RuneError && p.pt.w == 0 { 1337 + // EOF - see utf8.DecodeRune 1338 + p.failAt(false, p.pt.position, ".") 1339 + return nil, false 1340 + } 1341 + start := p.pt 1342 + p.read() 1343 + p.failAt(true, start.position, ".") 1344 + return p.sliceFrom(start), true 1345 + } 1346 + 1347 + func (p *parser) parseCharClassMatcher(chr *charClassMatcher) (interface{}, bool) { 1348 + if p.debug { 1349 + defer p.out(p.in("parseCharClassMatcher")) 1350 + } 1351 + 1352 + cur := p.pt.rn 1353 + start := p.pt 1354 + 1355 + // can't match EOF 1356 + if cur == utf8.RuneError && p.pt.w == 0 { // see utf8.DecodeRune 1357 + p.failAt(false, start.position, chr.val) 1358 + return nil, false 1359 + } 1360 + 1361 + if chr.ignoreCase { 1362 + cur = unicode.ToLower(cur) 1363 + } 1364 + 1365 + // try to match in the list of available chars 1366 + for _, rn := range chr.chars { 1367 + if rn == cur { 1368 + if chr.inverted { 1369 + p.failAt(false, start.position, chr.val) 1370 + return nil, false 1371 + } 1372 + p.read() 1373 + p.failAt(true, start.position, chr.val) 1374 + return p.sliceFrom(start), true 1375 + } 1376 + } 1377 + 1378 + // try to match in the list of ranges 1379 + for i := 0; i < len(chr.ranges); i += 2 { 1380 + if cur >= chr.ranges[i] && cur <= chr.ranges[i+1] { 1381 + if chr.inverted { 1382 + p.failAt(false, start.position, chr.val) 1383 + return nil, false 1384 + } 1385 + p.read() 1386 + p.failAt(true, start.position, chr.val) 1387 + return p.sliceFrom(start), true 1388 + } 1389 + } 1390 + 1391 + // try to match in the list of Unicode classes 1392 + for _, cl := range chr.classes { 1393 + if unicode.Is(cl, cur) { 1394 + if chr.inverted { 1395 + p.failAt(false, start.position, chr.val) 1396 + return nil, false 1397 + } 1398 + p.read() 1399 + p.failAt(true, start.position, chr.val) 1400 + return p.sliceFrom(start), true 1401 + } 1402 + } 1403 + 1404 + if chr.inverted { 1405 + p.read() 1406 + p.failAt(true, start.position, chr.val) 1407 + return p.sliceFrom(start), true 1408 + } 1409 + p.failAt(false, start.position, chr.val) 1410 + return nil, false 1411 + } 1412 + 1413 + func (p *parser) incChoiceAltCnt(ch *choiceExpr, altI int) { 1414 + choiceIdent := fmt.Sprintf("%s %d:%d", p.rstack[len(p.rstack)-1].name, ch.pos.line, ch.pos.col) 1415 + m := p.ChoiceAltCnt[choiceIdent] 1416 + if m == nil { 1417 + m = make(map[string]int) 1418 + p.ChoiceAltCnt[choiceIdent] = m 1419 + } 1420 + // We increment altI by 1, so the keys do not start at 0 1421 + alt := strconv.Itoa(altI + 1) 1422 + if altI == choiceNoMatch { 1423 + alt = p.choiceNoMatch 1424 + } 1425 + m[alt]++ 1426 + } 1427 + 1428 + func (p *parser) parseChoiceExpr(ch *choiceExpr) (interface{}, bool) { 1429 + if p.debug { 1430 + defer p.out(p.in("parseChoiceExpr")) 1431 + } 1432 + 1433 + for altI, alt := range ch.alternatives { 1434 + // dummy assignment to prevent compile error if optimized 1435 + _ = altI 1436 + 1437 + state := p.cloneState() 1438 + 1439 + p.pushV() 1440 + val, ok := p.parseExpr(alt) 1441 + p.popV() 1442 + if ok { 1443 + p.incChoiceAltCnt(ch, altI) 1444 + return val, ok 1445 + } 1446 + p.restoreState(state) 1447 + } 1448 + p.incChoiceAltCnt(ch, choiceNoMatch) 1449 + return nil, false 1450 + } 1451 + 1452 + func (p *parser) parseLabeledExpr(lab *labeledExpr) (interface{}, bool) { 1453 + if p.debug { 1454 + defer p.out(p.in("parseLabeledExpr")) 1455 + } 1456 + 1457 + p.pushV() 1458 + val, ok := p.parseExpr(lab.expr) 1459 + p.popV() 1460 + if ok && lab.label != "" { 1461 + m := p.vstack[len(p.vstack)-1] 1462 + m[lab.label] = val 1463 + } 1464 + return val, ok 1465 + } 1466 + 1467 + func (p *parser) parseLitMatcher(lit *litMatcher) (interface{}, bool) { 1468 + if p.debug { 1469 + defer p.out(p.in("parseLitMatcher")) 1470 + } 1471 + 1472 + start := p.pt 1473 + for _, want := range lit.val { 1474 + cur := p.pt.rn 1475 + if lit.ignoreCase { 1476 + cur = unicode.ToLower(cur) 1477 + } 1478 + if cur != want { 1479 + p.failAt(false, start.position, lit.want) 1480 + p.restore(start) 1481 + return nil, false 1482 + } 1483 + p.read() 1484 + } 1485 + p.failAt(true, start.position, lit.want) 1486 + return p.sliceFrom(start), true 1487 + } 1488 + 1489 + func (p *parser) parseNotCodeExpr(not *notCodeExpr) (interface{}, bool) { 1490 + if p.debug { 1491 + defer p.out(p.in("parseNotCodeExpr")) 1492 + } 1493 + 1494 + state := p.cloneState() 1495 + 1496 + ok, err := not.run(p) 1497 + if err != nil { 1498 + p.addErr(err) 1499 + } 1500 + p.restoreState(state) 1501 + 1502 + return nil, !ok 1503 + } 1504 + 1505 + func (p *parser) parseNotExpr(not *notExpr) (interface{}, bool) { 1506 + if p.debug { 1507 + defer p.out(p.in("parseNotExpr")) 1508 + } 1509 + 1510 + pt := p.pt 1511 + state := p.cloneState() 1512 + p.pushV() 1513 + p.maxFailInvertExpected = !p.maxFailInvertExpected 1514 + _, ok := p.parseExpr(not.expr) 1515 + p.maxFailInvertExpected = !p.maxFailInvertExpected 1516 + p.popV() 1517 + p.restoreState(state) 1518 + p.restore(pt) 1519 + 1520 + return nil, !ok 1521 + } 1522 + 1523 + func (p *parser) parseOneOrMoreExpr(expr *oneOrMoreExpr) (interface{}, bool) { 1524 + if p.debug { 1525 + defer p.out(p.in("parseOneOrMoreExpr")) 1526 + } 1527 + 1528 + var vals []interface{} 1529 + 1530 + for { 1531 + p.pushV() 1532 + val, ok := p.parseExpr(expr.expr) 1533 + p.popV() 1534 + if !ok { 1535 + if len(vals) == 0 { 1536 + // did not match once, no match 1537 + return nil, false 1538 + } 1539 + return vals, true 1540 + } 1541 + vals = append(vals, val) 1542 + } 1543 + } 1544 + 1545 + func (p *parser) parseRecoveryExpr(recover *recoveryExpr) (interface{}, bool) { 1546 + if p.debug { 1547 + defer p.out(p.in("parseRecoveryExpr (" + strings.Join(recover.failureLabel, ",") + ")")) 1548 + } 1549 + 1550 + p.pushRecovery(recover.failureLabel, recover.recoverExpr) 1551 + val, ok := p.parseExpr(recover.expr) 1552 + p.popRecovery() 1553 + 1554 + return val, ok 1555 + } 1556 + 1557 + func (p *parser) parseRuleRefExpr(ref *ruleRefExpr) (interface{}, bool) { 1558 + if p.debug { 1559 + defer p.out(p.in("parseRuleRefExpr " + ref.name)) 1560 + } 1561 + 1562 + if ref.name == "" { 1563 + panic(fmt.Sprintf("%s: invalid rule: missing name", ref.pos)) 1564 + } 1565 + 1566 + rule := p.rules[ref.name] 1567 + if rule == nil { 1568 + p.addErr(fmt.Errorf("undefined rule: %s", ref.name)) 1569 + return nil, false 1570 + } 1571 + return p.parseRule(rule) 1572 + } 1573 + 1574 + func (p *parser) parseSeqExpr(seq *seqExpr) (interface{}, bool) { 1575 + if p.debug { 1576 + defer p.out(p.in("parseSeqExpr")) 1577 + } 1578 + 1579 + vals := make([]interface{}, 0, len(seq.exprs)) 1580 + 1581 + pt := p.pt 1582 + state := p.cloneState() 1583 + for _, expr := range seq.exprs { 1584 + val, ok := p.parseExpr(expr) 1585 + if !ok { 1586 + p.restoreState(state) 1587 + p.restore(pt) 1588 + return nil, false 1589 + } 1590 + vals = append(vals, val) 1591 + } 1592 + return vals, true 1593 + } 1594 + 1595 + func (p *parser) parseStateCodeExpr(state *stateCodeExpr) (interface{}, bool) { 1596 + if p.debug { 1597 + defer p.out(p.in("parseStateCodeExpr")) 1598 + } 1599 + 1600 + err := state.run(p) 1601 + if err != nil { 1602 + p.addErr(err) 1603 + } 1604 + return nil, true 1605 + } 1606 + 1607 + func (p *parser) parseThrowExpr(expr *throwExpr) (interface{}, bool) { 1608 + if p.debug { 1609 + defer p.out(p.in("parseThrowExpr")) 1610 + } 1611 + 1612 + for i := len(p.recoveryStack) - 1; i >= 0; i-- { 1613 + if recoverExpr, ok := p.recoveryStack[i][expr.label]; ok { 1614 + if val, ok := p.parseExpr(recoverExpr); ok { 1615 + return val, ok 1616 + } 1617 + } 1618 + } 1619 + 1620 + return nil, false 1621 + } 1622 + 1623 + func (p *parser) parseZeroOrMoreExpr(expr *zeroOrMoreExpr) (interface{}, bool) { 1624 + if p.debug { 1625 + defer p.out(p.in("parseZeroOrMoreExpr")) 1626 + } 1627 + 1628 + var vals []interface{} 1629 + 1630 + for { 1631 + p.pushV() 1632 + val, ok := p.parseExpr(expr.expr) 1633 + p.popV() 1634 + if !ok { 1635 + return vals, true 1636 + } 1637 + vals = append(vals, val) 1638 + } 1639 + } 1640 + 1641 + func (p *parser) parseZeroOrOneExpr(expr *zeroOrOneExpr) (interface{}, bool) { 1642 + if p.debug { 1643 + defer p.out(p.in("parseZeroOrOneExpr")) 1644 + } 1645 + 1646 + p.pushV() 1647 + val, _ := p.parseExpr(expr.expr) 1648 + p.popV() 1649 + // whether it matched or not, consider it a match 1650 + return val, true 1651 + }
+22
parser/main.go
··· 1 + package parser 2 + 3 + import ( 4 + "fmt" 5 + "os" 6 + ) 7 + 8 + func main() { 9 + if len(os.Args) < 2 { 10 + fmt.Println("Please provide a Taskwarrior add command to parse") 11 + return 12 + } 13 + 14 + input := os.Args[1] 15 + result, err := Parse("", []byte(input)) 16 + if err != nil { 17 + fmt.Printf("Error parsing input: %v\n", err) 18 + return 19 + } 20 + 21 + fmt.Printf("Parsed result: %+v\n", result) 22 + }