├── LICENSE ├── Makefile ├── README.md ├── go.mod ├── go.sum ├── handwritten ├── convert.go ├── main.go ├── main_test.go └── type_parser.go ├── participle ├── convert.go ├── main.go └── main_test.go ├── phpdoc └── phpdoc.go ├── phpdoctest └── phpdoctest.go ├── yacc ├── lexer.go ├── main.go ├── main_test.go ├── phpdoc.y └── y.go └── yacc_ragel ├── lexer.go ├── main.go ├── main_test.go ├── phpdoc.rl ├── phpdoc.y └── y.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Iskander (Alex) Sharipov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | yacc_parser: 2 | @cd yacc && goyacc -p Phpdoc phpdoc.y && rm y.output 3 | 4 | yacc_ragel_parser: 5 | @cd yacc_ragel && goyacc -p Phpdoc phpdoc.y && rm y.output 6 | @cd yacc_ragel && ragel -Z -G2 phpdoc.rl -o lexer.go 7 | 8 | all: yacc_parser yacc_ragel_parser 9 | 10 | test: 11 | @go test -v -race -count 2 ./participle 12 | @go test -v -race -count 2 ./yacc 13 | @go test -v -race -count 2 ./yacc_ragel 14 | @go test -v -race -count 2 ./handwritten 15 | 16 | bench: 17 | @go test -bench=. -benchmem ./participle 18 | @go test -bench=. -benchmem ./yacc 19 | @go test -bench=. -benchmem ./yacc_ragel 20 | @go test -bench=. -benchmem ./handwritten 21 | 22 | .PHONY: yacc_parser yacc_ragel_parser test bench all 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Parsing & Go 2 | 3 | > This repository provides examples I referred to in "Parsing & Go" talk 4 | 5 | Parsing phpdoc type expressions using several approaches: 6 | 7 | * [participle](/participle) example uses [alecthomas/participle](https://github.com/alecthomas/participle) with default lexer 8 | * [yacc](/yacc) example uses [goyacc](https://pkg.go.dev/golang.org/x/tools/cmd/goyacc) with [text/scanner](https://pkg.go.dev/text/scanner) for lexing 9 | * [yacc_ragel](/yacc_ragel) example uses [goyacc](https://pkg.go.dev/golang.org/x/tools/cmd/goyacc) with a lexer generated with [Ragel](https://github.com/adrian-thurston/ragel) 10 | * [handwritten](/handwritten) example shows a manually created parser from [NoVerify](https://github.com/VKCOM/noverify) static analyzer 11 | 12 | These example parsers understand these types (and their combinations): 13 | 14 | | Type | Example | 15 | |---|---| 16 | | primitive type | `int`, `float`, `null`, ... | 17 | | type name | `Foo`, `Foo\Bar` | 18 | | nullable type | `?T` | 19 | | optional key type | `T?` | 20 | | array type | `T[]` | 21 | | union type | `X\|Y` | 22 | | intersection type | `X&Y` | 23 | 24 | Package [phpdoc](/phpdoc) defines the common AST constructed by every parser. 25 | 26 | Package [phpdoctest](/phpdoctest) contains test cases that are used to test every parser. 27 | 28 | Every parser package is a `main` that can parse a command-line argument: 29 | 30 | ```bash 31 | go run ./participle 'int|Foo\Bar' 32 | 33 | go run ./yacc 'int|Foo\Bar' 34 | 35 | go run ./yacc_ragel 'int|Foo\Bar' 36 | 37 | go run ./handwritten 'int|Foo\Bar' 38 | ``` 39 | 40 | Some useful `make` commands: 41 | 42 | ```bash 43 | # build all parsers (requires goyacc and ragel installed) 44 | make all 45 | 46 | # run all tests 47 | make test 48 | 49 | # run all benchmarks 50 | make bench 51 | ``` 52 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/quasilyte/parsing-in-go 2 | 3 | go 1.16 4 | 5 | require github.com/alecthomas/participle/v2 v2.0.0-alpha6 // indirect 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alecthomas/participle/v2 v2.0.0-alpha6 h1:6IeFBBLWi0xcTk4ModH9UKkLBYf5l5OzaYkJOjZW1rg= 2 | github.com/alecthomas/participle/v2 v2.0.0-alpha6/go.mod h1:Z1zPLDbcGsVsBYsThKXY00i84575bN/nMczzIrU4rWU= 3 | github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 8 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 10 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 11 | -------------------------------------------------------------------------------- /handwritten/convert.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/quasilyte/parsing-in-go/phpdoc" 8 | ) 9 | 10 | type converter struct { 11 | } 12 | 13 | func (conv *converter) convertUnion(dst *phpdoc.UnionType, args []TypeExpr) { 14 | dst.X = conv.Convert(args[0]) 15 | args = args[1:] 16 | if len(args) >= 2 { 17 | nested := &phpdoc.UnionType{} 18 | conv.convertUnion(nested, args) 19 | dst.Y = nested 20 | } else if len(args) == 1 { 21 | dst.Y = conv.Convert(args[0]) 22 | } 23 | } 24 | 25 | func (conv *converter) convertInter(dst *phpdoc.IntersectionType, args []TypeExpr) { 26 | dst.X = conv.Convert(args[0]) 27 | args = args[1:] 28 | if len(args) >= 2 { 29 | nested := &phpdoc.IntersectionType{} 30 | conv.convertInter(nested, args) 31 | dst.Y = nested 32 | } else if len(args) == 1 { 33 | dst.Y = conv.Convert(args[0]) 34 | } 35 | } 36 | 37 | func (conv *converter) Convert(expr TypeExpr) phpdoc.Type { 38 | switch expr.Kind { 39 | case ExprName: 40 | switch expr.Value { 41 | case `int`, `float`, `string`, `bool`, `false`, `null`: 42 | return &phpdoc.PrimitiveTypeName{Name: expr.Value} 43 | default: 44 | return &phpdoc.TypeName{Parts: strings.Split(expr.Value, `\`)} 45 | } 46 | case ExprParen: 47 | return conv.Convert(expr.Args[0]) 48 | case ExprNullable: 49 | return &phpdoc.NullableType{Elem: conv.Convert(expr.Args[0])} 50 | case ExprArray: 51 | return &phpdoc.ArrayType{Elem: conv.Convert(expr.Args[0])} 52 | case ExprOptional: 53 | return &phpdoc.OptionalKeyType{Elem: conv.Convert(expr.Args[0])} 54 | case ExprUnion: 55 | union := &phpdoc.UnionType{} 56 | conv.convertUnion(union, expr.Args) 57 | return union 58 | case ExprInter: 59 | inter := &phpdoc.IntersectionType{} 60 | conv.convertInter(inter, expr.Args) 61 | return inter 62 | default: 63 | fmt.Println("unhandled " + expr.Value) 64 | } 65 | return nil 66 | } 67 | -------------------------------------------------------------------------------- /handwritten/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | flag.Parse() 12 | 13 | parser := NewTypeParser() 14 | result := parser.Parse(flag.Args()[0]) 15 | 16 | encoder := json.NewEncoder(os.Stdout) 17 | encoder.SetIndent("", " ") 18 | if err := encoder.Encode(result); err != nil { 19 | panic(err) 20 | } 21 | 22 | var conv converter 23 | ast := conv.Convert(result.Expr) 24 | fmt.Printf("%#v\n", ast) 25 | fmt.Printf("%s\n", ast) 26 | } 27 | -------------------------------------------------------------------------------- /handwritten/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/quasilyte/parsing-in-go/phpdoc" 7 | "github.com/quasilyte/parsing-in-go/phpdoctest" 8 | ) 9 | 10 | type parserWrapper struct { 11 | parser *TypeParser 12 | } 13 | 14 | func (p *parserWrapper) Parse(s string) (phpdoc.Type, error) { 15 | typ := p.parser.Parse(s) 16 | var conv converter 17 | return conv.Convert(typ.Expr), nil 18 | } 19 | 20 | func TestMain(t *testing.T) { 21 | parser := &parserWrapper{ 22 | parser: NewTypeParser(), 23 | } 24 | phpdoctest.Run(t, parser) 25 | } 26 | 27 | func BenchmarkParser(b *testing.B) { 28 | parser := &parserWrapper{ 29 | parser: NewTypeParser(), 30 | } 31 | phpdoctest.RunBenchmark(b, parser) 32 | } 33 | -------------------------------------------------------------------------------- /handwritten/type_parser.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type ExprShape uint8 4 | 5 | type ExprKind uint8 6 | 7 | type Type struct { 8 | Source string 9 | Expr TypeExpr 10 | } 11 | 12 | func (typ Type) Clone() Type { 13 | return Type{Source: typ.Source, Expr: typ.Expr.Clone()} 14 | } 15 | 16 | func (typ Type) String() string { return typ.Source } 17 | 18 | func (typ Type) IsEmpty() bool { return typ.Expr.Value == "" } 19 | 20 | type TypeExpr struct { 21 | Kind ExprKind 22 | Shape ExprShape 23 | Begin uint16 24 | End uint16 25 | Value string 26 | Args []TypeExpr 27 | } 28 | 29 | func (e TypeExpr) Clone() TypeExpr { 30 | cloned := e 31 | cloned.Args = make([]TypeExpr, len(e.Args)) 32 | for i, a := range e.Args { 33 | cloned.Args[i] = a.Clone() 34 | } 35 | return cloned 36 | } 37 | 38 | //go:generate stringer -type=ExprKind -trimprefix=Expr 39 | const ( 40 | // ExprInvalid represents "failed to parse" type expression. 41 | ExprInvalid ExprKind = iota 42 | 43 | // ExprUnknown is a garbage-prefixed type expression. 44 | // Examples: `-int` `@@\Foo[]` 45 | // Args[0] - a valid expression that follows invalid prefix 46 | ExprUnknown 47 | 48 | // ExprName is a type that is identified by its name. 49 | // Examples: `int` `\Foo\Bar` `$this` 50 | ExprName 51 | 52 | // ExprSpecialName is a special name-like type node. 53 | // Examples: `*` `...` 54 | ExprSpecialName 55 | 56 | // ExprInt is a digit-only type expression. 57 | // Examples: `0` `10` 58 | ExprInt 59 | 60 | // ExprKeyVal is `key:val` type. 61 | // Examples: `name: string` `id:int` 62 | // Args[0] - key expression (left) 63 | // Args[1] - val expression (right) 64 | ExprKeyVal 65 | 66 | // ExprMemberType is access to member. 67 | // Examples: `\Foo::SOME_CONSTANT` `\Foo::$a` 68 | // Args[0] - class type expression (left) 69 | // Args[1] - member name expression (right) 70 | ExprMemberType 71 | 72 | // ExprArray is `elem[]` or `[]elem` type. 73 | // Examples: `int[]` `(int|float)[]` `int[` 74 | // Args[0] - array element type 75 | // ShapeArrayPrefix: `[]T` 76 | // 77 | // Note: may miss second `]`. 78 | ExprArray 79 | 80 | // ExprParen is `(expr)` type. 81 | // Examples: `(int)` `(\Foo\Bar[])` `(int` 82 | // Args[0] - enclosed type 83 | // 84 | // Note: may miss closing `)`. 85 | ExprParen 86 | 87 | // ExprNullable is `?expr` type. 88 | // Examples: `?int` `?\Foo` 89 | // Args[0] - nullable element type 90 | ExprNullable 91 | 92 | // ExprOptional is `expr?` type. 93 | // Examples: `k?: int` 94 | // Args[0] - optional element type 95 | ExprOptional 96 | 97 | // ExprNot is `!expr` type. 98 | // Examples: `!int` `!(int|float)` 99 | // Args[0] - negated element type 100 | // 101 | // Note: only valid for phpgrep type filters. 102 | ExprNot 103 | 104 | // ExprUnion is `x|y` type. 105 | // Examples: `T1|T2` `int|float[]|false` 106 | // Args - type variants 107 | ExprUnion 108 | 109 | // ExprInter is `x&y` type. 110 | // Examples: `T1&T2` `I1&I2&I3` 111 | ExprInter 112 | 113 | // ExprGeneric is a parametrized `expr` type. 114 | // Examples: `\Collection` `Either` `Bad`. 121 | ExprGeneric 122 | 123 | // ExprTypedCallable is a parametrized `callable(A,...):B` type. 124 | // Examples: `callable():void` `callable(int, int) : float` 125 | // Args[0] - return type 126 | // Args[1:] - argument types 127 | ExprTypedCallable 128 | ) 129 | 130 | const ( 131 | ShapeDefault ExprShape = iota 132 | ShapeArrayPrefix 133 | ShapeGenericParen 134 | ShapeGenericBrace 135 | ) 136 | 137 | var prefixPrecedenceTab = [256]byte{ 138 | '?': 5, 139 | '[': 5, 140 | '!': 5, 141 | } 142 | 143 | var infixPrecedenceTab = [256]byte{ 144 | ':': 1, 145 | '|': 2, 146 | ';': 3, // is :: 147 | '&': 4, 148 | '[': 5, 149 | '<': 6, 150 | '(': 6, 151 | '{': 6, 152 | '?': 5, 153 | } 154 | 155 | type TypeParser struct { 156 | input string 157 | pos uint 158 | skipUnknown bool 159 | insideGroup bool 160 | 161 | exprPool []TypeExpr 162 | allocated uint 163 | } 164 | 165 | func NewTypeParser() *TypeParser { 166 | return &TypeParser{ 167 | exprPool: make([]TypeExpr, 32), 168 | } 169 | } 170 | 171 | func (p *TypeParser) Parse(s string) Type { 172 | p.reset(s) 173 | p.skipWhitespace() 174 | typ := Type{Source: s, Expr: *p.parseExpr(0)} 175 | p.setValues(&typ.Expr) 176 | return typ 177 | } 178 | 179 | func (p *TypeParser) reset(input string) { 180 | p.input = input 181 | p.pos = 0 182 | p.allocated = 0 183 | p.skipUnknown = false 184 | } 185 | 186 | func (p *TypeParser) exprValue(e *TypeExpr) string { 187 | return p.input[e.Begin:e.End] 188 | } 189 | 190 | func (p *TypeParser) setValues(e *TypeExpr) { 191 | for i := range e.Args { 192 | p.setValues(&e.Args[i]) 193 | } 194 | e.Value = p.exprValue(e) 195 | } 196 | 197 | func (p *TypeParser) parseExprInsideGroup() *TypeExpr { 198 | insideGroup := p.insideGroup 199 | p.insideGroup = true 200 | expr := p.parseExpr(0) 201 | p.insideGroup = insideGroup 202 | return expr 203 | } 204 | 205 | func (p *TypeParser) parseExpr(precedence byte) *TypeExpr { 206 | if p.insideGroup { 207 | p.skipWhitespace() 208 | } 209 | 210 | var left *TypeExpr 211 | begin := uint16(p.pos) 212 | ch := p.nextByte() 213 | 214 | switch { 215 | case ch == '$' || ch == '\\' || p.isFirstIdentChar(ch): 216 | for p.isNameChar(p.peek()) { 217 | p.pos++ 218 | } 219 | left = p.newExpr(ExprName, begin, uint16(p.pos)) 220 | case p.isDigit(ch): 221 | for p.isDigit(p.peek()) { 222 | p.pos++ 223 | } 224 | left = p.newExpr(ExprInt, begin, uint16(p.pos)) 225 | case ch == '[': 226 | if p.peek() == ']' { 227 | p.pos++ 228 | } 229 | elem := p.parseExpr(prefixPrecedenceTab['[']) 230 | left = p.newExprShape(ExprArray, ShapeArrayPrefix, begin, uint16(p.pos), elem) 231 | case ch == '(': 232 | if p.peek() == ')' { 233 | p.pos++ 234 | expr := p.newExpr(ExprInvalid, begin+1, begin+1) 235 | left = p.newExpr(ExprParen, begin, uint16(p.pos), expr) 236 | break 237 | } 238 | expr := p.parseExprInsideGroup() 239 | if p.peek() == ')' { 240 | p.pos++ 241 | } 242 | left = p.newExpr(ExprParen, begin, uint16(p.pos), expr) 243 | case ch == '?': 244 | elem := p.parseExpr(prefixPrecedenceTab['?']) 245 | left = p.newExpr(ExprNullable, begin, uint16(p.pos), elem) 246 | case ch == '!': 247 | elem := p.parseExpr(prefixPrecedenceTab['!']) 248 | left = p.newExpr(ExprNot, begin, uint16(p.pos), elem) 249 | case ch == '*': 250 | left = p.newExpr(ExprSpecialName, begin, uint16(p.pos)) 251 | case ch == '.' && p.peekAt(p.pos+0) == '.' && p.peekAt(p.pos+1) == '.': 252 | p.pos += uint(len("..")) 253 | left = p.newExpr(ExprSpecialName, begin, uint16(p.pos)) 254 | case ch == ' ': 255 | left = p.newExpr(ExprInvalid, begin, uint16(p.pos)) 256 | default: 257 | // Try to handle invalid expressions somehow and continue 258 | // the parsing of valid expressions. 259 | if p.skipUnknown { 260 | return nil 261 | } 262 | p.skipUnknown = true 263 | for p.peek() != 0 { 264 | // Stop if we found infix or postfix token and emit invalid expr. 265 | // Stop if we found something that looks like a terminating token. 266 | ch := p.peek() 267 | if infixPrecedenceTab[ch] != 0 || ch == ')' || ch == '>' || ch == ']' || ch == ' ' { 268 | left = p.newExpr(ExprInvalid, begin, uint16(p.pos)) 269 | break 270 | } 271 | pos := p.pos 272 | // Stop if we found a valid expression. 273 | x := p.parseExpr(0) 274 | if x != nil { 275 | left = p.newExpr(ExprUnknown, begin, uint16(p.pos), x) 276 | break 277 | } 278 | // Try again from the next byte pos. 279 | p.pos = pos + 1 280 | } 281 | p.skipUnknown = false 282 | // Found nothing, emit invalid expr. 283 | if left == nil { 284 | left = p.newExpr(ExprInvalid, begin, uint16(p.pos)) 285 | } 286 | } 287 | 288 | if p.insideGroup { 289 | p.skipWhitespace() 290 | } 291 | 292 | calcPrecedence := func() byte { 293 | prc := infixPrecedenceTab[p.peek()] 294 | if p.peek() == ':' { 295 | ch := p.peekAt(p.pos + 1) 296 | if ch == ':' { 297 | prc = 3 298 | } 299 | } 300 | return prc 301 | } 302 | 303 | for precedence < calcPrecedence() { 304 | ch := p.nextByte() 305 | switch ch { 306 | case '?': 307 | left = p.newExpr(ExprOptional, begin, uint16(p.pos), left) 308 | case ':': 309 | isMemberType := p.peek() == ':' 310 | if isMemberType { 311 | _ = p.nextByte() 312 | right := p.parseExpr(infixPrecedenceTab[';']) 313 | left = p.newExpr(ExprMemberType, begin, uint16(p.pos), left, right) 314 | } else { 315 | right := p.parseExpr(infixPrecedenceTab[':']) 316 | left = p.newExpr(ExprKeyVal, begin, uint16(p.pos), left, right) 317 | } 318 | case '[': 319 | if p.peek() == ']' { 320 | p.pos++ 321 | } 322 | left = p.newExpr(ExprArray, begin, uint16(p.pos), left) 323 | case '|': 324 | var right *TypeExpr 325 | switch p.peek() { 326 | case 0, ')': 327 | right = p.newExpr(ExprInvalid, uint16(p.pos), uint16(p.pos)) 328 | default: 329 | right = p.parseExpr(infixPrecedenceTab['|']) 330 | } 331 | if left.Kind == ExprUnion { 332 | left.Args = append(left.Args, *right) 333 | left.End = right.End 334 | } else { 335 | left = p.newExpr(ExprUnion, begin, right.End, left, right) 336 | } 337 | case '&': 338 | var right *TypeExpr 339 | switch p.peek() { 340 | case 0, ')': 341 | right = p.newExpr(ExprInvalid, uint16(p.pos), uint16(p.pos)) 342 | default: 343 | right = p.parseExpr(infixPrecedenceTab['&']) 344 | } 345 | if left.Kind == ExprInter { 346 | left.Args = append(left.Args, *right) 347 | left.End = right.End 348 | } else { 349 | left = p.newExpr(ExprInter, begin, right.End, left, right) 350 | } 351 | case '<', '(', '{': 352 | endCh := byte('>') 353 | shape := ShapeDefault 354 | switch ch { 355 | case '(': 356 | endCh = ')' 357 | shape = ShapeGenericParen 358 | case '{': 359 | endCh = '}' 360 | shape = ShapeGenericBrace 361 | } 362 | left = p.newExprShape(ExprGeneric, shape, begin, left.End, left) 363 | for { 364 | p.skipWhitespace() 365 | ch := p.peek() 366 | if ch == 0 { 367 | break 368 | } 369 | if ch == endCh { 370 | p.pos++ 371 | break 372 | } 373 | x := p.parseExprInsideGroup() 374 | left.Args = append(left.Args, *x) 375 | p.skipWhitespace() 376 | if p.peek() == ',' { 377 | p.pos++ 378 | } 379 | } 380 | // For `callable(...)` case we want to see whether we can peek ':'. 381 | // If we can, parse it as a typed callable. 382 | if shape == ShapeGenericParen && p.exprValue(&left.Args[0]) == "callable" { 383 | pos := p.pos 384 | p.skipWhitespace() 385 | if p.peek() == ':' { 386 | p.pos++ 387 | returnType := p.parseExprInsideGroup() 388 | left.Args[0] = *returnType 389 | left.Kind = ExprTypedCallable 390 | left.Shape = ShapeDefault 391 | } else { 392 | p.pos = pos // Unread whitespace 393 | } 394 | } 395 | left.End = uint16(p.pos) 396 | } 397 | } 398 | 399 | return left 400 | } 401 | 402 | func (p *TypeParser) newExprShape(kind ExprKind, shape ExprShape, begin, end uint16, args ...*TypeExpr) *TypeExpr { 403 | e := p.newExpr(kind, begin, end, args...) 404 | e.Shape = shape 405 | return e 406 | } 407 | 408 | func (p *TypeParser) newExpr(kind ExprKind, begin, end uint16, args ...*TypeExpr) *TypeExpr { 409 | e := p.allocExpr() 410 | *e = TypeExpr{ 411 | Kind: kind, 412 | Begin: begin, 413 | End: end, 414 | Args: e.Args[:0], 415 | } 416 | for _, arg := range args { 417 | e.Args = append(e.Args, *arg) 418 | } 419 | return e 420 | } 421 | 422 | func (p *TypeParser) allocExpr() *TypeExpr { 423 | i := p.allocated 424 | if i < uint(len(p.exprPool)) { 425 | p.allocated++ 426 | return &p.exprPool[i] 427 | } 428 | return &TypeExpr{} 429 | } 430 | 431 | func (p *TypeParser) isDigit(ch byte) bool { 432 | return ch >= '0' && ch <= '9' 433 | } 434 | 435 | func (p *TypeParser) isNameChar(ch byte) bool { 436 | // [\\a-zA-Z_\x7f-\xff0-9] and '-' 437 | return ch == '\\' || p.isFirstIdentChar(ch) || p.isDigit(ch) || ch == '-' 438 | } 439 | 440 | func (p *TypeParser) isFirstIdentChar(ch byte) bool { 441 | // [a-zA-Z_\x7f-\xff] 442 | return (ch >= 'a' && ch <= 'z') || 443 | (ch >= 'A' && ch <= 'Z') || 444 | ch == '_' || 445 | (ch >= 0x7f && ch <= 0xff) 446 | } 447 | 448 | func (p *TypeParser) nextByte() byte { 449 | if p.pos < uint(len(p.input)) { 450 | i := p.pos 451 | p.pos++ 452 | return p.input[i] 453 | } 454 | return 0 455 | } 456 | 457 | func (p *TypeParser) peekAt(pos uint) byte { 458 | if pos < uint(len(p.input)) { 459 | return p.input[pos] 460 | } 461 | return 0 462 | } 463 | 464 | func (p *TypeParser) peek() byte { 465 | return p.peekAt(p.pos) 466 | } 467 | 468 | func (p *TypeParser) skipWhitespace() { 469 | for p.peek() == ' ' { 470 | p.pos++ 471 | } 472 | } 473 | -------------------------------------------------------------------------------- /participle/convert.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/quasilyte/parsing-in-go/phpdoc" 5 | ) 6 | 7 | type converter struct{} 8 | 9 | func (conv *converter) Convert(expr *UnionExpr) phpdoc.Type { 10 | return conv.convertUnion(expr) 11 | } 12 | 13 | func (conv *converter) convertPrefix(expr *PrefixExpr) phpdoc.Type { 14 | result := conv.convertPrimary(expr.Right) 15 | if len(expr.Ops) != 0 { 16 | for _, prefix := range expr.Ops { 17 | switch prefix.Tok { 18 | case "?": 19 | result = &phpdoc.NullableType{Elem: result} 20 | } 21 | } 22 | } 23 | // if expr.Postfix != nil { 24 | 25 | // } 26 | return result 27 | } 28 | 29 | func (conv *converter) convertPostfix(expr *PostfixExpr) phpdoc.Type { 30 | result := conv.convertPrefix(expr.Left) 31 | if len(expr.Ops) != 0 { 32 | for _, postfix := range expr.Ops { 33 | switch postfix.Tok { 34 | case "[]": 35 | result = &phpdoc.ArrayType{Elem: result} 36 | case "?": 37 | result = &phpdoc.OptionalKeyType{Elem: result} 38 | } 39 | } 40 | } 41 | return result 42 | } 43 | 44 | func (conv *converter) convertUnion(expr *UnionExpr) phpdoc.Type { 45 | left := conv.convertIntersection(expr.Left) 46 | if expr.Right != nil { 47 | right := conv.convertUnion(expr.Right) 48 | return &phpdoc.UnionType{X: left, Y: right} 49 | } 50 | return left 51 | } 52 | 53 | func (conv *converter) convertIntersection(expr *IntersectionExpr) phpdoc.Type { 54 | left := conv.convertPostfix(expr.Left) 55 | if expr.Right != nil { 56 | right := conv.convertIntersection(expr.Right) 57 | return &phpdoc.IntersectionType{X: left, Y: right} 58 | } 59 | return left 60 | } 61 | 62 | func (conv *converter) convertPrimary(expr *PrimaryExpr) phpdoc.Type { 63 | if expr.TypeName != nil { 64 | if expr.TypeName.Primitive != nil { 65 | return &phpdoc.PrimitiveTypeName{Name: *expr.TypeName.Primitive} 66 | } 67 | return conv.convertClassName(expr.TypeName.Class, nil) 68 | } 69 | if expr.Parens != nil { 70 | return conv.Convert(expr.Parens) 71 | } 72 | return nil 73 | } 74 | 75 | func (conv *converter) convertClassName(name *ClassNameExpr, parts []string) phpdoc.Type { 76 | parts = append(parts, name.Part) 77 | if name.Next == nil { 78 | return &phpdoc.TypeName{Parts: parts} 79 | } 80 | return conv.convertClassName(name.Next, parts) 81 | } 82 | -------------------------------------------------------------------------------- /participle/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "os" 8 | 9 | "github.com/alecthomas/participle/v2" 10 | ) 11 | 12 | type PrimaryExpr struct { 13 | TypeName *TypeNameExpr `@@` 14 | Parens *UnionExpr `| "(" @@ ")"` 15 | } 16 | 17 | type TypeNameExpr struct { 18 | Primitive *string `@("null"|"false"|"int"|"float"|"string"|"bool")` 19 | Class *ClassNameExpr `| @@` 20 | } 21 | 22 | type ClassNameExpr struct { 23 | Part string `@Ident` 24 | Next *ClassNameExpr `("\\" @@)?` 25 | } 26 | 27 | type PrefixExpr struct { 28 | Ops []*PrefixOp `@@*` 29 | Right *PrimaryExpr `@@` 30 | } 31 | 32 | type PrefixOp struct { 33 | Tok string `@("?")` 34 | } 35 | 36 | type PostfixExpr struct { 37 | Left *PrefixExpr `@@` 38 | Ops []*PostfixOp `@@*` 39 | } 40 | 41 | type PostfixOp struct { 42 | Tok string `@("[" "]" | "?")` 43 | } 44 | 45 | type IntersectionExpr struct { 46 | Left *PostfixExpr `@@` 47 | Right *IntersectionExpr `("&" @@)?` 48 | } 49 | 50 | type UnionExpr struct { 51 | Left *IntersectionExpr `@@` 52 | Right *UnionExpr `("|" @@)?` 53 | } 54 | 55 | func main() { 56 | flag.Parse() 57 | 58 | parser := participle.MustBuild(&UnionExpr{}) 59 | var result UnionExpr 60 | if err := parser.ParseString("", flag.Args()[0], &result); err != nil { 61 | panic(err) 62 | } 63 | 64 | encoder := json.NewEncoder(os.Stdout) 65 | encoder.SetIndent("", " ") 66 | if err := encoder.Encode(result); err != nil { 67 | panic(err) 68 | } 69 | 70 | var conv converter 71 | ast := conv.Convert(&result) 72 | fmt.Printf("%#v\n", ast) 73 | fmt.Printf("%s\n", ast) 74 | } 75 | -------------------------------------------------------------------------------- /participle/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/alecthomas/participle/v2" 7 | "github.com/quasilyte/parsing-in-go/phpdoc" 8 | "github.com/quasilyte/parsing-in-go/phpdoctest" 9 | ) 10 | 11 | type parserWrapper struct { 12 | parser *participle.Parser 13 | } 14 | 15 | func (p *parserWrapper) Parse(s string) (phpdoc.Type, error) { 16 | var result UnionExpr 17 | if err := p.parser.ParseString("", s, &result); err != nil { 18 | return nil, err 19 | } 20 | var conv converter 21 | return conv.Convert(&result), nil 22 | } 23 | 24 | func TestMain(t *testing.T) { 25 | parser := &parserWrapper{ 26 | parser: participle.MustBuild(&UnionExpr{}), 27 | } 28 | phpdoctest.Run(t, parser) 29 | } 30 | 31 | func BenchmarkParser(b *testing.B) { 32 | parser := &parserWrapper{ 33 | parser: participle.MustBuild(&UnionExpr{}), 34 | } 35 | phpdoctest.RunBenchmark(b, parser) 36 | } 37 | -------------------------------------------------------------------------------- /phpdoc/phpdoc.go: -------------------------------------------------------------------------------- 1 | package phpdoc 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type Type interface { 9 | String() string 10 | } 11 | 12 | type PrimitiveTypeName struct { 13 | Name string 14 | } 15 | 16 | type TypeName struct { 17 | Parts []string 18 | } 19 | 20 | type NullableType struct { 21 | Elem Type 22 | } 23 | 24 | type OptionalKeyType struct { 25 | Elem Type 26 | } 27 | 28 | type ArrayType struct { 29 | Elem Type 30 | } 31 | 32 | type UnionType struct { 33 | X Type 34 | Y Type 35 | } 36 | 37 | type IntersectionType struct { 38 | X Type 39 | Y Type 40 | } 41 | 42 | func (typ *PrimitiveTypeName) String() string { return typ.Name } 43 | func (typ *TypeName) String() string { return strings.Join(typ.Parts, `\`) } 44 | func (typ *NullableType) String() string { return "?(" + typ.Elem.String() + ")" } 45 | func (typ *OptionalKeyType) String() string { return "(" + typ.Elem.String() + ")?" } 46 | func (typ *ArrayType) String() string { return "(" + typ.Elem.String() + ")[]" } 47 | func (typ *UnionType) String() string { return fmt.Sprintf("(%s)|(%s)", typ.X, typ.Y) } 48 | func (typ *IntersectionType) String() string { return fmt.Sprintf("(%s)&(%s)", typ.X, typ.Y) } 49 | -------------------------------------------------------------------------------- /phpdoctest/phpdoctest.go: -------------------------------------------------------------------------------- 1 | package phpdoctest 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/quasilyte/parsing-in-go/phpdoc" 7 | ) 8 | 9 | type parser interface { 10 | Parse(s string) (phpdoc.Type, error) 11 | } 12 | 13 | func RunBenchmark(b *testing.B, p parser) { 14 | tests := []struct { 15 | label string 16 | input string 17 | }{ 18 | {`simple`, `int`}, 19 | {`normal`, `Foo|Bar|null`}, 20 | {`complex`, `(?a|c|false)&d`}, 21 | {`array`, `int[][]`}, 22 | {`classname`, `A\B\C\D`}, 23 | {`whitespace`, ` ( int ) `}, 24 | } 25 | 26 | for _, test := range tests { 27 | input := test.input 28 | b.Run(test.label, func(b *testing.B) { 29 | for i := 0; i < b.N; i++ { 30 | _, err := p.Parse(input) 31 | if err != nil { 32 | b.Fatal(err) 33 | } 34 | } 35 | }) 36 | } 37 | } 38 | 39 | func Run(t *testing.T, p parser) { 40 | tests := []struct { 41 | input string 42 | expect string 43 | }{ 44 | {`int`, `int`}, 45 | {`float`, `float`}, 46 | {`string`, `string`}, 47 | {`bool`, `bool`}, 48 | 49 | {` ( int ) `, `int`}, 50 | 51 | {`A`, `A`}, 52 | {`A\B`, `A\B`}, 53 | {`A\B\C`, `A\B\C`}, 54 | 55 | {`(int)`, `int`}, 56 | {`((int))`, `int`}, 57 | 58 | {`?int`, `?(int)`}, 59 | {`??int`, `?(?(int))`}, 60 | {`?x[]`, `(?(x))[]`}, 61 | {`?x[][]`, `((?(x))[])[]`}, 62 | {`?(int[])[]`, `(?((int)[]))[]`}, 63 | 64 | {`int?`, `(int)?`}, 65 | {`(int[])[]?`, `(((int)[])[])?`}, 66 | 67 | {`int[][]`, `((int)[])[]`}, 68 | {`(int)[][]`, `((int)[])[]`}, 69 | {`(int[])[]`, `((int)[])[]`}, 70 | 71 | {`int|null[]`, `(int)|((null)[])`}, 72 | 73 | {`int|float[]`, `(int)|((float)[])`}, 74 | {`(int|float)[]`, `((int)|(float))[]`}, 75 | 76 | {`int&float[]`, `(int)&((float)[])`}, 77 | {`(int&float)[]`, `((int)&(float))[]`}, 78 | 79 | {`x|y|z`, `(x)|((y)|(z))`}, 80 | {`x&y&z`, `(x)&((y)&(z))`}, 81 | {`x&y|z`, `((x)&(y))|(z)`}, 82 | {`x|y&z`, `(x)|((y)&(z))`}, 83 | {`x&(y|z)`, `(x)&((y)|(z))`}, 84 | {`x&(y|z)[]`, `(x)&(((y)|(z))[])`}, 85 | {`x&(y|z)[][]`, `(x)&((((y)|(z))[])[])`}, 86 | 87 | {`?int|Reader&Writer|false`, `(?(int))|(((Reader)&(Writer))|(false))`}, 88 | } 89 | 90 | for _, test := range tests { 91 | typ, err := p.Parse(` ` + test.input + ` `) 92 | if err != nil { 93 | t.Errorf("parse `%s` error: %v", test.input, err) 94 | continue 95 | } 96 | if typ == nil { 97 | t.Errorf("parse `%s` returned type is nil!", test.input) 98 | continue 99 | } 100 | if typ.String() != test.expect { 101 | t.Errorf("parse `%s`:\nhave `%s`\nwant `%s`", 102 | test.input, typ.String(), test.expect) 103 | continue 104 | } 105 | typ2, err := p.Parse(typ.String()) 106 | if err != nil { 107 | t.Errorf("re-parse `%s` error: %v", typ.String(), err) 108 | continue 109 | } 110 | if typ.String() != typ2.String() { 111 | t.Errorf("re-parsed representation mismatch:\nA: `%s`\nB: `%s`", 112 | typ.String(), typ2.String()) 113 | continue 114 | } 115 | 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /yacc/lexer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "text/scanner" 7 | "unicode" 8 | 9 | "github.com/quasilyte/parsing-in-go/phpdoc" 10 | ) 11 | 12 | type PhpdocLex struct { 13 | s scanner.Scanner 14 | result phpdoc.Type 15 | } 16 | 17 | func NewLexer() *PhpdocLex { 18 | lexer := &PhpdocLex{} 19 | lexer.s.IsIdentRune = func(ch rune, i int) bool { 20 | return unicode.IsLetter(ch) || 21 | (unicode.IsDigit(ch) && i > 0) || 22 | ch == '\\' 23 | } 24 | return lexer 25 | } 26 | 27 | func (l *PhpdocLex) Init(s string) { 28 | l.s.Init(strings.NewReader(s)) 29 | } 30 | 31 | var nameToToken = map[string]rune{ 32 | "int": T_INT, 33 | "float": T_FLOAT, 34 | "null": T_NULL, 35 | "string": T_STRING, 36 | "false": T_FALSE, 37 | "bool": T_BOOL, 38 | } 39 | 40 | func (l *PhpdocLex) nextToken(sym *PhpdocSymType) rune { 41 | tok := l.s.Scan() 42 | if tok == scanner.Ident { 43 | text := l.s.TokenText() 44 | tok, ok := nameToToken[text] 45 | if ok { 46 | return tok 47 | } 48 | sym.text = text 49 | return T_NAME 50 | } 51 | return tok 52 | } 53 | 54 | func (l *PhpdocLex) Lex(sym *PhpdocSymType) int { 55 | tok := l.nextToken(sym) 56 | sym.tok = tok 57 | return int(tok) 58 | } 59 | 60 | func (l *PhpdocLex) Error(s string) { 61 | fmt.Printf("syntax error: %s\n", s) 62 | } 63 | -------------------------------------------------------------------------------- /yacc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | func main() { 10 | flag.Parse() 11 | 12 | lexer := NewLexer() 13 | lexer.s.Init(strings.NewReader(flag.Args()[0])) 14 | parser := PhpdocParserImpl{} 15 | status := parser.Parse(lexer) 16 | fmt.Println(status) 17 | ast := lexer.result 18 | fmt.Printf("%#v\n", ast) 19 | fmt.Printf("%s\n", ast) 20 | } 21 | -------------------------------------------------------------------------------- /yacc/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/quasilyte/parsing-in-go/phpdoc" 7 | "github.com/quasilyte/parsing-in-go/phpdoctest" 8 | ) 9 | 10 | type parserWrapper struct { 11 | parser PhpdocParserImpl 12 | } 13 | 14 | func (p *parserWrapper) Parse(s string) (phpdoc.Type, error) { 15 | lexer := NewLexer() 16 | lexer.Init(s) 17 | p.parser.Parse(lexer) 18 | return lexer.result, nil 19 | } 20 | 21 | func TestMain(t *testing.T) { 22 | parser := &parserWrapper{} 23 | phpdoctest.Run(t, parser) 24 | } 25 | 26 | func BenchmarkParser(b *testing.B) { 27 | parser := &parserWrapper{} 28 | phpdoctest.RunBenchmark(b, parser) 29 | } 30 | -------------------------------------------------------------------------------- /yacc/phpdoc.y: -------------------------------------------------------------------------------- 1 | %{ 2 | package main 3 | 4 | import ( 5 | "github.com/quasilyte/parsing-in-go/phpdoc" 6 | "strings" 7 | ) 8 | 9 | %} 10 | 11 | %union{ 12 | tok rune 13 | expr phpdoc.Type 14 | text string 15 | } 16 | 17 | %token T_NULL 18 | %token T_FALSE 19 | %token T_INT 20 | %token T_FLOAT 21 | %token T_STRING 22 | %token T_BOOL 23 | %token T_NAME 24 | 25 | %right '|' 26 | %right '&' 27 | %left OPTIONAL 28 | %right '[' 29 | %left '?' 30 | 31 | %type type_expr 32 | %type primitive_type 33 | 34 | %start start 35 | 36 | %% 37 | 38 | start 39 | : type_expr { Phpdoclex.(*PhpdocLex).result = $1 } 40 | ; 41 | 42 | type_expr 43 | : primitive_type { $$ = $1 } 44 | | T_NAME { $$ = &phpdoc.TypeName{Parts: strings.Split($1, `\`)} } 45 | | '(' type_expr ')' { $$ = $2 } 46 | | '?' type_expr { $$ = &phpdoc.NullableType{Elem: $2} } 47 | | type_expr '[' ']' { $$ = &phpdoc.ArrayType{Elem: $1} } 48 | | type_expr '?' %prec OPTIONAL { $$ = &phpdoc.OptionalKeyType{Elem: $1} } 49 | | type_expr '&' type_expr { $$ = &phpdoc.IntersectionType{X: $1, Y: $3} } 50 | | type_expr '|' type_expr { $$ = &phpdoc.UnionType{X: $1, Y: $3} } 51 | ; 52 | 53 | primitive_type 54 | : T_NULL { $$ = &phpdoc.PrimitiveTypeName{Name: "null"} } 55 | | T_FALSE { $$ = &phpdoc.PrimitiveTypeName{Name: "false"} } 56 | | T_INT { $$ = &phpdoc.PrimitiveTypeName{Name: "int"} } 57 | | T_FLOAT { $$ = &phpdoc.PrimitiveTypeName{Name: "float"} } 58 | | T_STRING { $$ = &phpdoc.PrimitiveTypeName{Name: "string"} } 59 | | T_BOOL { $$ = &phpdoc.PrimitiveTypeName{Name: "bool"} } 60 | ; 61 | 62 | %% 63 | -------------------------------------------------------------------------------- /yacc/y.go: -------------------------------------------------------------------------------- 1 | // Code generated by goyacc -p Phpdoc phpdoc.y. DO NOT EDIT. 2 | 3 | //line phpdoc.y:2 4 | package main 5 | 6 | import __yyfmt__ "fmt" 7 | 8 | //line phpdoc.y:2 9 | 10 | import ( 11 | "github.com/quasilyte/parsing-in-go/phpdoc" 12 | "strings" 13 | ) 14 | 15 | //line phpdoc.y:11 16 | type PhpdocSymType struct { 17 | yys int 18 | tok rune 19 | expr phpdoc.Type 20 | text string 21 | } 22 | 23 | const T_NULL = 57346 24 | const T_FALSE = 57347 25 | const T_INT = 57348 26 | const T_FLOAT = 57349 27 | const T_STRING = 57350 28 | const T_BOOL = 57351 29 | const T_NAME = 57352 30 | const OPTIONAL = 57353 31 | 32 | var PhpdocToknames = [...]string{ 33 | "$end", 34 | "error", 35 | "$unk", 36 | "T_NULL", 37 | "T_FALSE", 38 | "T_INT", 39 | "T_FLOAT", 40 | "T_STRING", 41 | "T_BOOL", 42 | "T_NAME", 43 | "'|'", 44 | "'&'", 45 | "OPTIONAL", 46 | "'['", 47 | "'?'", 48 | "'('", 49 | "')'", 50 | "']'", 51 | } 52 | var PhpdocStatenames = [...]string{} 53 | 54 | const PhpdocEofCode = 1 55 | const PhpdocErrCode = 2 56 | const PhpdocInitialStackSize = 16 57 | 58 | //line phpdoc.y:62 59 | 60 | //line yacctab:1 61 | var PhpdocExca = [...]int{ 62 | -1, 1, 63 | 1, -1, 64 | -2, 0, 65 | } 66 | 67 | const PhpdocPrivate = 57344 68 | 69 | const PhpdocLast = 37 70 | 71 | var PhpdocAct = [...]int{ 72 | 73 | 7, 8, 9, 10, 11, 12, 4, 19, 1, 3, 74 | 0, 6, 5, 16, 15, 2, 13, 14, 0, 22, 75 | 0, 17, 18, 16, 15, 0, 13, 14, 0, 0, 76 | 0, 20, 21, 15, 0, 13, 14, 77 | } 78 | var PhpdocPact = [...]int{ 79 | 80 | -4, -1000, 12, -1000, -1000, -4, -4, -1000, -1000, -1000, 81 | -1000, -1000, -1000, -11, -1000, -4, -4, 2, -1000, -1000, 82 | 21, 12, -1000, 83 | } 84 | var PhpdocPgo = [...]int{ 85 | 86 | 0, 15, 9, 8, 87 | } 88 | var PhpdocR1 = [...]int{ 89 | 90 | 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 91 | 2, 2, 2, 2, 2, 2, 92 | } 93 | var PhpdocR2 = [...]int{ 94 | 95 | 0, 1, 1, 1, 3, 2, 3, 2, 3, 3, 96 | 1, 1, 1, 1, 1, 1, 97 | } 98 | var PhpdocChk = [...]int{ 99 | 100 | -1000, -3, -1, -2, 10, 16, 15, 4, 5, 6, 101 | 7, 8, 9, 14, 15, 12, 11, -1, -1, 18, 102 | -1, -1, 17, 103 | } 104 | var PhpdocDef = [...]int{ 105 | 106 | 0, -2, 1, 2, 3, 0, 0, 10, 11, 12, 107 | 13, 14, 15, 0, 7, 0, 0, 0, 5, 6, 108 | 8, 9, 4, 109 | } 110 | var PhpdocTok1 = [...]int{ 111 | 112 | 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 113 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 114 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 115 | 3, 3, 3, 3, 3, 3, 3, 3, 12, 3, 116 | 16, 17, 3, 3, 3, 3, 3, 3, 3, 3, 117 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 118 | 3, 3, 3, 15, 3, 3, 3, 3, 3, 3, 119 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 120 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 121 | 3, 14, 3, 18, 3, 3, 3, 3, 3, 3, 122 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 123 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 124 | 3, 3, 3, 3, 11, 125 | } 126 | var PhpdocTok2 = [...]int{ 127 | 128 | 2, 3, 4, 5, 6, 7, 8, 9, 10, 13, 129 | } 130 | var PhpdocTok3 = [...]int{ 131 | 0, 132 | } 133 | 134 | var PhpdocErrorMessages = [...]struct { 135 | state int 136 | token int 137 | msg string 138 | }{} 139 | 140 | //line yaccpar:1 141 | 142 | /* parser for yacc output */ 143 | 144 | var ( 145 | PhpdocDebug = 0 146 | PhpdocErrorVerbose = false 147 | ) 148 | 149 | type PhpdocLexer interface { 150 | Lex(lval *PhpdocSymType) int 151 | Error(s string) 152 | } 153 | 154 | type PhpdocParser interface { 155 | Parse(PhpdocLexer) int 156 | Lookahead() int 157 | } 158 | 159 | type PhpdocParserImpl struct { 160 | lval PhpdocSymType 161 | stack [PhpdocInitialStackSize]PhpdocSymType 162 | char int 163 | } 164 | 165 | func (p *PhpdocParserImpl) Lookahead() int { 166 | return p.char 167 | } 168 | 169 | func PhpdocNewParser() PhpdocParser { 170 | return &PhpdocParserImpl{} 171 | } 172 | 173 | const PhpdocFlag = -1000 174 | 175 | func PhpdocTokname(c int) string { 176 | if c >= 1 && c-1 < len(PhpdocToknames) { 177 | if PhpdocToknames[c-1] != "" { 178 | return PhpdocToknames[c-1] 179 | } 180 | } 181 | return __yyfmt__.Sprintf("tok-%v", c) 182 | } 183 | 184 | func PhpdocStatname(s int) string { 185 | if s >= 0 && s < len(PhpdocStatenames) { 186 | if PhpdocStatenames[s] != "" { 187 | return PhpdocStatenames[s] 188 | } 189 | } 190 | return __yyfmt__.Sprintf("state-%v", s) 191 | } 192 | 193 | func PhpdocErrorMessage(state, lookAhead int) string { 194 | const TOKSTART = 4 195 | 196 | if !PhpdocErrorVerbose { 197 | return "syntax error" 198 | } 199 | 200 | for _, e := range PhpdocErrorMessages { 201 | if e.state == state && e.token == lookAhead { 202 | return "syntax error: " + e.msg 203 | } 204 | } 205 | 206 | res := "syntax error: unexpected " + PhpdocTokname(lookAhead) 207 | 208 | // To match Bison, suggest at most four expected tokens. 209 | expected := make([]int, 0, 4) 210 | 211 | // Look for shiftable tokens. 212 | base := PhpdocPact[state] 213 | for tok := TOKSTART; tok-1 < len(PhpdocToknames); tok++ { 214 | if n := base + tok; n >= 0 && n < PhpdocLast && PhpdocChk[PhpdocAct[n]] == tok { 215 | if len(expected) == cap(expected) { 216 | return res 217 | } 218 | expected = append(expected, tok) 219 | } 220 | } 221 | 222 | if PhpdocDef[state] == -2 { 223 | i := 0 224 | for PhpdocExca[i] != -1 || PhpdocExca[i+1] != state { 225 | i += 2 226 | } 227 | 228 | // Look for tokens that we accept or reduce. 229 | for i += 2; PhpdocExca[i] >= 0; i += 2 { 230 | tok := PhpdocExca[i] 231 | if tok < TOKSTART || PhpdocExca[i+1] == 0 { 232 | continue 233 | } 234 | if len(expected) == cap(expected) { 235 | return res 236 | } 237 | expected = append(expected, tok) 238 | } 239 | 240 | // If the default action is to accept or reduce, give up. 241 | if PhpdocExca[i+1] != 0 { 242 | return res 243 | } 244 | } 245 | 246 | for i, tok := range expected { 247 | if i == 0 { 248 | res += ", expecting " 249 | } else { 250 | res += " or " 251 | } 252 | res += PhpdocTokname(tok) 253 | } 254 | return res 255 | } 256 | 257 | func Phpdoclex1(lex PhpdocLexer, lval *PhpdocSymType) (char, token int) { 258 | token = 0 259 | char = lex.Lex(lval) 260 | if char <= 0 { 261 | token = PhpdocTok1[0] 262 | goto out 263 | } 264 | if char < len(PhpdocTok1) { 265 | token = PhpdocTok1[char] 266 | goto out 267 | } 268 | if char >= PhpdocPrivate { 269 | if char < PhpdocPrivate+len(PhpdocTok2) { 270 | token = PhpdocTok2[char-PhpdocPrivate] 271 | goto out 272 | } 273 | } 274 | for i := 0; i < len(PhpdocTok3); i += 2 { 275 | token = PhpdocTok3[i+0] 276 | if token == char { 277 | token = PhpdocTok3[i+1] 278 | goto out 279 | } 280 | } 281 | 282 | out: 283 | if token == 0 { 284 | token = PhpdocTok2[1] /* unknown char */ 285 | } 286 | if PhpdocDebug >= 3 { 287 | __yyfmt__.Printf("lex %s(%d)\n", PhpdocTokname(token), uint(char)) 288 | } 289 | return char, token 290 | } 291 | 292 | func PhpdocParse(Phpdoclex PhpdocLexer) int { 293 | return PhpdocNewParser().Parse(Phpdoclex) 294 | } 295 | 296 | func (Phpdocrcvr *PhpdocParserImpl) Parse(Phpdoclex PhpdocLexer) int { 297 | var Phpdocn int 298 | var PhpdocVAL PhpdocSymType 299 | var PhpdocDollar []PhpdocSymType 300 | _ = PhpdocDollar // silence set and not used 301 | PhpdocS := Phpdocrcvr.stack[:] 302 | 303 | Nerrs := 0 /* number of errors */ 304 | Errflag := 0 /* error recovery flag */ 305 | Phpdocstate := 0 306 | Phpdocrcvr.char = -1 307 | Phpdoctoken := -1 // Phpdocrcvr.char translated into internal numbering 308 | defer func() { 309 | // Make sure we report no lookahead when not parsing. 310 | Phpdocstate = -1 311 | Phpdocrcvr.char = -1 312 | Phpdoctoken = -1 313 | }() 314 | Phpdocp := -1 315 | goto Phpdocstack 316 | 317 | ret0: 318 | return 0 319 | 320 | ret1: 321 | return 1 322 | 323 | Phpdocstack: 324 | /* put a state and value onto the stack */ 325 | if PhpdocDebug >= 4 { 326 | __yyfmt__.Printf("char %v in %v\n", PhpdocTokname(Phpdoctoken), PhpdocStatname(Phpdocstate)) 327 | } 328 | 329 | Phpdocp++ 330 | if Phpdocp >= len(PhpdocS) { 331 | nyys := make([]PhpdocSymType, len(PhpdocS)*2) 332 | copy(nyys, PhpdocS) 333 | PhpdocS = nyys 334 | } 335 | PhpdocS[Phpdocp] = PhpdocVAL 336 | PhpdocS[Phpdocp].yys = Phpdocstate 337 | 338 | Phpdocnewstate: 339 | Phpdocn = PhpdocPact[Phpdocstate] 340 | if Phpdocn <= PhpdocFlag { 341 | goto Phpdocdefault /* simple state */ 342 | } 343 | if Phpdocrcvr.char < 0 { 344 | Phpdocrcvr.char, Phpdoctoken = Phpdoclex1(Phpdoclex, &Phpdocrcvr.lval) 345 | } 346 | Phpdocn += Phpdoctoken 347 | if Phpdocn < 0 || Phpdocn >= PhpdocLast { 348 | goto Phpdocdefault 349 | } 350 | Phpdocn = PhpdocAct[Phpdocn] 351 | if PhpdocChk[Phpdocn] == Phpdoctoken { /* valid shift */ 352 | Phpdocrcvr.char = -1 353 | Phpdoctoken = -1 354 | PhpdocVAL = Phpdocrcvr.lval 355 | Phpdocstate = Phpdocn 356 | if Errflag > 0 { 357 | Errflag-- 358 | } 359 | goto Phpdocstack 360 | } 361 | 362 | Phpdocdefault: 363 | /* default state action */ 364 | Phpdocn = PhpdocDef[Phpdocstate] 365 | if Phpdocn == -2 { 366 | if Phpdocrcvr.char < 0 { 367 | Phpdocrcvr.char, Phpdoctoken = Phpdoclex1(Phpdoclex, &Phpdocrcvr.lval) 368 | } 369 | 370 | /* look through exception table */ 371 | xi := 0 372 | for { 373 | if PhpdocExca[xi+0] == -1 && PhpdocExca[xi+1] == Phpdocstate { 374 | break 375 | } 376 | xi += 2 377 | } 378 | for xi += 2; ; xi += 2 { 379 | Phpdocn = PhpdocExca[xi+0] 380 | if Phpdocn < 0 || Phpdocn == Phpdoctoken { 381 | break 382 | } 383 | } 384 | Phpdocn = PhpdocExca[xi+1] 385 | if Phpdocn < 0 { 386 | goto ret0 387 | } 388 | } 389 | if Phpdocn == 0 { 390 | /* error ... attempt to resume parsing */ 391 | switch Errflag { 392 | case 0: /* brand new error */ 393 | Phpdoclex.Error(PhpdocErrorMessage(Phpdocstate, Phpdoctoken)) 394 | Nerrs++ 395 | if PhpdocDebug >= 1 { 396 | __yyfmt__.Printf("%s", PhpdocStatname(Phpdocstate)) 397 | __yyfmt__.Printf(" saw %s\n", PhpdocTokname(Phpdoctoken)) 398 | } 399 | fallthrough 400 | 401 | case 1, 2: /* incompletely recovered error ... try again */ 402 | Errflag = 3 403 | 404 | /* find a state where "error" is a legal shift action */ 405 | for Phpdocp >= 0 { 406 | Phpdocn = PhpdocPact[PhpdocS[Phpdocp].yys] + PhpdocErrCode 407 | if Phpdocn >= 0 && Phpdocn < PhpdocLast { 408 | Phpdocstate = PhpdocAct[Phpdocn] /* simulate a shift of "error" */ 409 | if PhpdocChk[Phpdocstate] == PhpdocErrCode { 410 | goto Phpdocstack 411 | } 412 | } 413 | 414 | /* the current p has no shift on "error", pop stack */ 415 | if PhpdocDebug >= 2 { 416 | __yyfmt__.Printf("error recovery pops state %d\n", PhpdocS[Phpdocp].yys) 417 | } 418 | Phpdocp-- 419 | } 420 | /* there is no state on the stack with an error shift ... abort */ 421 | goto ret1 422 | 423 | case 3: /* no shift yet; clobber input char */ 424 | if PhpdocDebug >= 2 { 425 | __yyfmt__.Printf("error recovery discards %s\n", PhpdocTokname(Phpdoctoken)) 426 | } 427 | if Phpdoctoken == PhpdocEofCode { 428 | goto ret1 429 | } 430 | Phpdocrcvr.char = -1 431 | Phpdoctoken = -1 432 | goto Phpdocnewstate /* try again in the same state */ 433 | } 434 | } 435 | 436 | /* reduction by production Phpdocn */ 437 | if PhpdocDebug >= 2 { 438 | __yyfmt__.Printf("reduce %v in:\n\t%v\n", Phpdocn, PhpdocStatname(Phpdocstate)) 439 | } 440 | 441 | Phpdocnt := Phpdocn 442 | Phpdocpt := Phpdocp 443 | _ = Phpdocpt // guard against "declared and not used" 444 | 445 | Phpdocp -= PhpdocR2[Phpdocn] 446 | // Phpdocp is now the index of $0. Perform the default action. Iff the 447 | // reduced production is ε, $1 is possibly out of range. 448 | if Phpdocp+1 >= len(PhpdocS) { 449 | nyys := make([]PhpdocSymType, len(PhpdocS)*2) 450 | copy(nyys, PhpdocS) 451 | PhpdocS = nyys 452 | } 453 | PhpdocVAL = PhpdocS[Phpdocp+1] 454 | 455 | /* consult goto table to find next state */ 456 | Phpdocn = PhpdocR1[Phpdocn] 457 | Phpdocg := PhpdocPgo[Phpdocn] 458 | Phpdocj := Phpdocg + PhpdocS[Phpdocp].yys + 1 459 | 460 | if Phpdocj >= PhpdocLast { 461 | Phpdocstate = PhpdocAct[Phpdocg] 462 | } else { 463 | Phpdocstate = PhpdocAct[Phpdocj] 464 | if PhpdocChk[Phpdocstate] != -Phpdocn { 465 | Phpdocstate = PhpdocAct[Phpdocg] 466 | } 467 | } 468 | // dummy call; replaced with literal code 469 | switch Phpdocnt { 470 | 471 | case 1: 472 | PhpdocDollar = PhpdocS[Phpdocpt-1 : Phpdocpt+1] 473 | //line phpdoc.y:39 474 | { 475 | Phpdoclex.(*PhpdocLex).result = PhpdocDollar[1].expr 476 | } 477 | case 2: 478 | PhpdocDollar = PhpdocS[Phpdocpt-1 : Phpdocpt+1] 479 | //line phpdoc.y:43 480 | { 481 | PhpdocVAL.expr = PhpdocDollar[1].expr 482 | } 483 | case 3: 484 | PhpdocDollar = PhpdocS[Phpdocpt-1 : Phpdocpt+1] 485 | //line phpdoc.y:44 486 | { 487 | PhpdocVAL.expr = &phpdoc.TypeName{Parts: strings.Split(PhpdocDollar[1].text, `\`)} 488 | } 489 | case 4: 490 | PhpdocDollar = PhpdocS[Phpdocpt-3 : Phpdocpt+1] 491 | //line phpdoc.y:45 492 | { 493 | PhpdocVAL.expr = PhpdocDollar[2].expr 494 | } 495 | case 5: 496 | PhpdocDollar = PhpdocS[Phpdocpt-2 : Phpdocpt+1] 497 | //line phpdoc.y:46 498 | { 499 | PhpdocVAL.expr = &phpdoc.NullableType{Elem: PhpdocDollar[2].expr} 500 | } 501 | case 6: 502 | PhpdocDollar = PhpdocS[Phpdocpt-3 : Phpdocpt+1] 503 | //line phpdoc.y:47 504 | { 505 | PhpdocVAL.expr = &phpdoc.ArrayType{Elem: PhpdocDollar[1].expr} 506 | } 507 | case 7: 508 | PhpdocDollar = PhpdocS[Phpdocpt-2 : Phpdocpt+1] 509 | //line phpdoc.y:48 510 | { 511 | PhpdocVAL.expr = &phpdoc.OptionalKeyType{Elem: PhpdocDollar[1].expr} 512 | } 513 | case 8: 514 | PhpdocDollar = PhpdocS[Phpdocpt-3 : Phpdocpt+1] 515 | //line phpdoc.y:49 516 | { 517 | PhpdocVAL.expr = &phpdoc.IntersectionType{X: PhpdocDollar[1].expr, Y: PhpdocDollar[3].expr} 518 | } 519 | case 9: 520 | PhpdocDollar = PhpdocS[Phpdocpt-3 : Phpdocpt+1] 521 | //line phpdoc.y:50 522 | { 523 | PhpdocVAL.expr = &phpdoc.UnionType{X: PhpdocDollar[1].expr, Y: PhpdocDollar[3].expr} 524 | } 525 | case 10: 526 | PhpdocDollar = PhpdocS[Phpdocpt-1 : Phpdocpt+1] 527 | //line phpdoc.y:54 528 | { 529 | PhpdocVAL.expr = &phpdoc.PrimitiveTypeName{Name: "null"} 530 | } 531 | case 11: 532 | PhpdocDollar = PhpdocS[Phpdocpt-1 : Phpdocpt+1] 533 | //line phpdoc.y:55 534 | { 535 | PhpdocVAL.expr = &phpdoc.PrimitiveTypeName{Name: "false"} 536 | } 537 | case 12: 538 | PhpdocDollar = PhpdocS[Phpdocpt-1 : Phpdocpt+1] 539 | //line phpdoc.y:56 540 | { 541 | PhpdocVAL.expr = &phpdoc.PrimitiveTypeName{Name: "int"} 542 | } 543 | case 13: 544 | PhpdocDollar = PhpdocS[Phpdocpt-1 : Phpdocpt+1] 545 | //line phpdoc.y:57 546 | { 547 | PhpdocVAL.expr = &phpdoc.PrimitiveTypeName{Name: "float"} 548 | } 549 | case 14: 550 | PhpdocDollar = PhpdocS[Phpdocpt-1 : Phpdocpt+1] 551 | //line phpdoc.y:58 552 | { 553 | PhpdocVAL.expr = &phpdoc.PrimitiveTypeName{Name: "string"} 554 | } 555 | case 15: 556 | PhpdocDollar = PhpdocS[Phpdocpt-1 : Phpdocpt+1] 557 | //line phpdoc.y:59 558 | { 559 | PhpdocVAL.expr = &phpdoc.PrimitiveTypeName{Name: "bool"} 560 | } 561 | } 562 | goto Phpdocstack /* stack new state and value */ 563 | } 564 | -------------------------------------------------------------------------------- /yacc_ragel/lexer.go: -------------------------------------------------------------------------------- 1 | 2 | //line phpdoc.rl:1 3 | package main 4 | 5 | 6 | //line phpdoc.rl:4 7 | 8 | import ( 9 | "github.com/quasilyte/parsing-in-go/phpdoc" 10 | "fmt" 11 | ) 12 | 13 | type PhpdocLex struct { 14 | pos int 15 | src string 16 | result phpdoc.Type 17 | } 18 | 19 | func NewLexer() *PhpdocLex { 20 | return &PhpdocLex{} 21 | } 22 | 23 | func (l *PhpdocLex) Init(s string) { 24 | l.pos = 0 25 | l.src = s 26 | } 27 | 28 | func (l *PhpdocLex) Lex(lval *PhpdocSymType) int { 29 | tok := 0 30 | 31 | data := l.src 32 | cs, p, pe := 0, l.pos, len(data) 33 | eof := pe 34 | var ts, te int 35 | var act int 36 | 37 | 38 | //line phpdoc.rl:52 39 | 40 | 41 | 42 | //line lexer.go:43 43 | { 44 | cs = lexer_start 45 | ts = 0 46 | te = 0 47 | act = 0 48 | } 49 | 50 | //line phpdoc.rl:55 51 | 52 | //line lexer.go:53 53 | { 54 | if p == pe { 55 | goto _test_eof 56 | } 57 | switch cs { 58 | case 0: 59 | goto st_case_0 60 | case 1: 61 | goto st_case_1 62 | case 2: 63 | goto st_case_2 64 | case 3: 65 | goto st_case_3 66 | case 4: 67 | goto st_case_4 68 | case 5: 69 | goto st_case_5 70 | case 6: 71 | goto st_case_6 72 | case 7: 73 | goto st_case_7 74 | case 8: 75 | goto st_case_8 76 | case 9: 77 | goto st_case_9 78 | case 10: 79 | goto st_case_10 80 | case 11: 81 | goto st_case_11 82 | case 12: 83 | goto st_case_12 84 | case 13: 85 | goto st_case_13 86 | case 14: 87 | goto st_case_14 88 | case 15: 89 | goto st_case_15 90 | case 16: 91 | goto st_case_16 92 | case 17: 93 | goto st_case_17 94 | case 18: 95 | goto st_case_18 96 | case 19: 97 | goto st_case_19 98 | case 20: 99 | goto st_case_20 100 | case 21: 101 | goto st_case_21 102 | } 103 | goto st_out 104 | tr0: 105 | //line phpdoc.rl:50 106 | te = p+1 107 | { tok = int(data[ts]); {p++; cs = 0; goto _out } } 108 | goto st0 109 | tr1: 110 | //line phpdoc.rl:42 111 | te = p+1 112 | 113 | goto st0 114 | tr8: 115 | //line NONE:1 116 | switch act { 117 | case 2: 118 | {p = (te) - 1 119 | tok = T_INT; {p++; cs = 0; goto _out } } 120 | case 3: 121 | {p = (te) - 1 122 | tok = T_FLOAT; {p++; cs = 0; goto _out } } 123 | case 4: 124 | {p = (te) - 1 125 | tok = T_NULL; {p++; cs = 0; goto _out } } 126 | case 5: 127 | {p = (te) - 1 128 | tok = T_STRING; {p++; cs = 0; goto _out } } 129 | case 6: 130 | {p = (te) - 1 131 | tok = T_FALSE; {p++; cs = 0; goto _out } } 132 | case 7: 133 | {p = (te) - 1 134 | tok = T_BOOL; {p++; cs = 0; goto _out } } 135 | case 8: 136 | {p = (te) - 1 137 | tok = T_NAME; {p++; cs = 0; goto _out } } 138 | } 139 | 140 | goto st0 141 | tr9: 142 | //line phpdoc.rl:49 143 | te = p 144 | p-- 145 | { tok = T_NAME; {p++; cs = 0; goto _out } } 146 | goto st0 147 | st0: 148 | //line NONE:1 149 | ts = 0 150 | 151 | if p++; p == pe { 152 | goto _test_eof0 153 | } 154 | st_case_0: 155 | //line NONE:1 156 | ts = p 157 | 158 | //line lexer.go:159 159 | switch data[p] { 160 | case 9: 161 | goto tr1 162 | case 32: 163 | goto tr1 164 | case 96: 165 | goto tr0 166 | case 98: 167 | goto st2 168 | case 102: 169 | goto st5 170 | case 105: 171 | goto st12 172 | case 110: 173 | goto st14 174 | case 115: 175 | goto st17 176 | } 177 | switch { 178 | case data[p] < 91: 179 | if data[p] <= 64 { 180 | goto tr0 181 | } 182 | case data[p] > 94: 183 | if 123 <= data[p] && data[p] <= 127 { 184 | goto tr0 185 | } 186 | default: 187 | goto tr0 188 | } 189 | goto tr2 190 | tr2: 191 | //line NONE:1 192 | te = p+1 193 | 194 | //line phpdoc.rl:49 195 | act = 8; 196 | goto st1 197 | tr12: 198 | //line NONE:1 199 | te = p+1 200 | 201 | //line phpdoc.rl:48 202 | act = 7; 203 | goto st1 204 | tr17: 205 | //line NONE:1 206 | te = p+1 207 | 208 | //line phpdoc.rl:47 209 | act = 6; 210 | goto st1 211 | tr20: 212 | //line NONE:1 213 | te = p+1 214 | 215 | //line phpdoc.rl:44 216 | act = 3; 217 | goto st1 218 | tr22: 219 | //line NONE:1 220 | te = p+1 221 | 222 | //line phpdoc.rl:43 223 | act = 2; 224 | goto st1 225 | tr25: 226 | //line NONE:1 227 | te = p+1 228 | 229 | //line phpdoc.rl:45 230 | act = 4; 231 | goto st1 232 | tr30: 233 | //line NONE:1 234 | te = p+1 235 | 236 | //line phpdoc.rl:46 237 | act = 5; 238 | goto st1 239 | st1: 240 | if p++; p == pe { 241 | goto _test_eof1 242 | } 243 | st_case_1: 244 | //line lexer.go:245 245 | switch data[p] { 246 | case 91: 247 | goto tr8 248 | case 96: 249 | goto tr8 250 | } 251 | switch { 252 | case data[p] < 58: 253 | if data[p] <= 47 { 254 | goto tr8 255 | } 256 | case data[p] > 64: 257 | switch { 258 | case data[p] > 94: 259 | if 123 <= data[p] && data[p] <= 127 { 260 | goto tr8 261 | } 262 | case data[p] >= 93: 263 | goto tr8 264 | } 265 | default: 266 | goto tr8 267 | } 268 | goto tr2 269 | st2: 270 | if p++; p == pe { 271 | goto _test_eof2 272 | } 273 | st_case_2: 274 | switch data[p] { 275 | case 91: 276 | goto tr9 277 | case 96: 278 | goto tr9 279 | case 111: 280 | goto st3 281 | } 282 | switch { 283 | case data[p] < 58: 284 | if data[p] <= 47 { 285 | goto tr9 286 | } 287 | case data[p] > 64: 288 | switch { 289 | case data[p] > 94: 290 | if 123 <= data[p] && data[p] <= 127 { 291 | goto tr9 292 | } 293 | case data[p] >= 93: 294 | goto tr9 295 | } 296 | default: 297 | goto tr9 298 | } 299 | goto tr2 300 | st3: 301 | if p++; p == pe { 302 | goto _test_eof3 303 | } 304 | st_case_3: 305 | switch data[p] { 306 | case 91: 307 | goto tr9 308 | case 96: 309 | goto tr9 310 | case 111: 311 | goto st4 312 | } 313 | switch { 314 | case data[p] < 58: 315 | if data[p] <= 47 { 316 | goto tr9 317 | } 318 | case data[p] > 64: 319 | switch { 320 | case data[p] > 94: 321 | if 123 <= data[p] && data[p] <= 127 { 322 | goto tr9 323 | } 324 | case data[p] >= 93: 325 | goto tr9 326 | } 327 | default: 328 | goto tr9 329 | } 330 | goto tr2 331 | st4: 332 | if p++; p == pe { 333 | goto _test_eof4 334 | } 335 | st_case_4: 336 | switch data[p] { 337 | case 91: 338 | goto tr9 339 | case 96: 340 | goto tr9 341 | case 108: 342 | goto tr12 343 | } 344 | switch { 345 | case data[p] < 58: 346 | if data[p] <= 47 { 347 | goto tr9 348 | } 349 | case data[p] > 64: 350 | switch { 351 | case data[p] > 94: 352 | if 123 <= data[p] && data[p] <= 127 { 353 | goto tr9 354 | } 355 | case data[p] >= 93: 356 | goto tr9 357 | } 358 | default: 359 | goto tr9 360 | } 361 | goto tr2 362 | st5: 363 | if p++; p == pe { 364 | goto _test_eof5 365 | } 366 | st_case_5: 367 | switch data[p] { 368 | case 91: 369 | goto tr9 370 | case 96: 371 | goto tr9 372 | case 97: 373 | goto st6 374 | case 108: 375 | goto st9 376 | } 377 | switch { 378 | case data[p] < 58: 379 | if data[p] <= 47 { 380 | goto tr9 381 | } 382 | case data[p] > 64: 383 | switch { 384 | case data[p] > 94: 385 | if 123 <= data[p] && data[p] <= 127 { 386 | goto tr9 387 | } 388 | case data[p] >= 93: 389 | goto tr9 390 | } 391 | default: 392 | goto tr9 393 | } 394 | goto tr2 395 | st6: 396 | if p++; p == pe { 397 | goto _test_eof6 398 | } 399 | st_case_6: 400 | switch data[p] { 401 | case 91: 402 | goto tr9 403 | case 96: 404 | goto tr9 405 | case 108: 406 | goto st7 407 | } 408 | switch { 409 | case data[p] < 58: 410 | if data[p] <= 47 { 411 | goto tr9 412 | } 413 | case data[p] > 64: 414 | switch { 415 | case data[p] > 94: 416 | if 123 <= data[p] && data[p] <= 127 { 417 | goto tr9 418 | } 419 | case data[p] >= 93: 420 | goto tr9 421 | } 422 | default: 423 | goto tr9 424 | } 425 | goto tr2 426 | st7: 427 | if p++; p == pe { 428 | goto _test_eof7 429 | } 430 | st_case_7: 431 | switch data[p] { 432 | case 91: 433 | goto tr9 434 | case 96: 435 | goto tr9 436 | case 115: 437 | goto st8 438 | } 439 | switch { 440 | case data[p] < 58: 441 | if data[p] <= 47 { 442 | goto tr9 443 | } 444 | case data[p] > 64: 445 | switch { 446 | case data[p] > 94: 447 | if 123 <= data[p] && data[p] <= 127 { 448 | goto tr9 449 | } 450 | case data[p] >= 93: 451 | goto tr9 452 | } 453 | default: 454 | goto tr9 455 | } 456 | goto tr2 457 | st8: 458 | if p++; p == pe { 459 | goto _test_eof8 460 | } 461 | st_case_8: 462 | switch data[p] { 463 | case 91: 464 | goto tr9 465 | case 96: 466 | goto tr9 467 | case 101: 468 | goto tr17 469 | } 470 | switch { 471 | case data[p] < 58: 472 | if data[p] <= 47 { 473 | goto tr9 474 | } 475 | case data[p] > 64: 476 | switch { 477 | case data[p] > 94: 478 | if 123 <= data[p] && data[p] <= 127 { 479 | goto tr9 480 | } 481 | case data[p] >= 93: 482 | goto tr9 483 | } 484 | default: 485 | goto tr9 486 | } 487 | goto tr2 488 | st9: 489 | if p++; p == pe { 490 | goto _test_eof9 491 | } 492 | st_case_9: 493 | switch data[p] { 494 | case 91: 495 | goto tr9 496 | case 96: 497 | goto tr9 498 | case 111: 499 | goto st10 500 | } 501 | switch { 502 | case data[p] < 58: 503 | if data[p] <= 47 { 504 | goto tr9 505 | } 506 | case data[p] > 64: 507 | switch { 508 | case data[p] > 94: 509 | if 123 <= data[p] && data[p] <= 127 { 510 | goto tr9 511 | } 512 | case data[p] >= 93: 513 | goto tr9 514 | } 515 | default: 516 | goto tr9 517 | } 518 | goto tr2 519 | st10: 520 | if p++; p == pe { 521 | goto _test_eof10 522 | } 523 | st_case_10: 524 | switch data[p] { 525 | case 91: 526 | goto tr9 527 | case 96: 528 | goto tr9 529 | case 97: 530 | goto st11 531 | } 532 | switch { 533 | case data[p] < 58: 534 | if data[p] <= 47 { 535 | goto tr9 536 | } 537 | case data[p] > 64: 538 | switch { 539 | case data[p] > 94: 540 | if 123 <= data[p] && data[p] <= 127 { 541 | goto tr9 542 | } 543 | case data[p] >= 93: 544 | goto tr9 545 | } 546 | default: 547 | goto tr9 548 | } 549 | goto tr2 550 | st11: 551 | if p++; p == pe { 552 | goto _test_eof11 553 | } 554 | st_case_11: 555 | switch data[p] { 556 | case 91: 557 | goto tr9 558 | case 96: 559 | goto tr9 560 | case 116: 561 | goto tr20 562 | } 563 | switch { 564 | case data[p] < 58: 565 | if data[p] <= 47 { 566 | goto tr9 567 | } 568 | case data[p] > 64: 569 | switch { 570 | case data[p] > 94: 571 | if 123 <= data[p] && data[p] <= 127 { 572 | goto tr9 573 | } 574 | case data[p] >= 93: 575 | goto tr9 576 | } 577 | default: 578 | goto tr9 579 | } 580 | goto tr2 581 | st12: 582 | if p++; p == pe { 583 | goto _test_eof12 584 | } 585 | st_case_12: 586 | switch data[p] { 587 | case 91: 588 | goto tr9 589 | case 96: 590 | goto tr9 591 | case 110: 592 | goto st13 593 | } 594 | switch { 595 | case data[p] < 58: 596 | if data[p] <= 47 { 597 | goto tr9 598 | } 599 | case data[p] > 64: 600 | switch { 601 | case data[p] > 94: 602 | if 123 <= data[p] && data[p] <= 127 { 603 | goto tr9 604 | } 605 | case data[p] >= 93: 606 | goto tr9 607 | } 608 | default: 609 | goto tr9 610 | } 611 | goto tr2 612 | st13: 613 | if p++; p == pe { 614 | goto _test_eof13 615 | } 616 | st_case_13: 617 | switch data[p] { 618 | case 91: 619 | goto tr9 620 | case 96: 621 | goto tr9 622 | case 116: 623 | goto tr22 624 | } 625 | switch { 626 | case data[p] < 58: 627 | if data[p] <= 47 { 628 | goto tr9 629 | } 630 | case data[p] > 64: 631 | switch { 632 | case data[p] > 94: 633 | if 123 <= data[p] && data[p] <= 127 { 634 | goto tr9 635 | } 636 | case data[p] >= 93: 637 | goto tr9 638 | } 639 | default: 640 | goto tr9 641 | } 642 | goto tr2 643 | st14: 644 | if p++; p == pe { 645 | goto _test_eof14 646 | } 647 | st_case_14: 648 | switch data[p] { 649 | case 91: 650 | goto tr9 651 | case 96: 652 | goto tr9 653 | case 117: 654 | goto st15 655 | } 656 | switch { 657 | case data[p] < 58: 658 | if data[p] <= 47 { 659 | goto tr9 660 | } 661 | case data[p] > 64: 662 | switch { 663 | case data[p] > 94: 664 | if 123 <= data[p] && data[p] <= 127 { 665 | goto tr9 666 | } 667 | case data[p] >= 93: 668 | goto tr9 669 | } 670 | default: 671 | goto tr9 672 | } 673 | goto tr2 674 | st15: 675 | if p++; p == pe { 676 | goto _test_eof15 677 | } 678 | st_case_15: 679 | switch data[p] { 680 | case 91: 681 | goto tr9 682 | case 96: 683 | goto tr9 684 | case 108: 685 | goto st16 686 | } 687 | switch { 688 | case data[p] < 58: 689 | if data[p] <= 47 { 690 | goto tr9 691 | } 692 | case data[p] > 64: 693 | switch { 694 | case data[p] > 94: 695 | if 123 <= data[p] && data[p] <= 127 { 696 | goto tr9 697 | } 698 | case data[p] >= 93: 699 | goto tr9 700 | } 701 | default: 702 | goto tr9 703 | } 704 | goto tr2 705 | st16: 706 | if p++; p == pe { 707 | goto _test_eof16 708 | } 709 | st_case_16: 710 | switch data[p] { 711 | case 91: 712 | goto tr9 713 | case 96: 714 | goto tr9 715 | case 108: 716 | goto tr25 717 | } 718 | switch { 719 | case data[p] < 58: 720 | if data[p] <= 47 { 721 | goto tr9 722 | } 723 | case data[p] > 64: 724 | switch { 725 | case data[p] > 94: 726 | if 123 <= data[p] && data[p] <= 127 { 727 | goto tr9 728 | } 729 | case data[p] >= 93: 730 | goto tr9 731 | } 732 | default: 733 | goto tr9 734 | } 735 | goto tr2 736 | st17: 737 | if p++; p == pe { 738 | goto _test_eof17 739 | } 740 | st_case_17: 741 | switch data[p] { 742 | case 91: 743 | goto tr9 744 | case 96: 745 | goto tr9 746 | case 116: 747 | goto st18 748 | } 749 | switch { 750 | case data[p] < 58: 751 | if data[p] <= 47 { 752 | goto tr9 753 | } 754 | case data[p] > 64: 755 | switch { 756 | case data[p] > 94: 757 | if 123 <= data[p] && data[p] <= 127 { 758 | goto tr9 759 | } 760 | case data[p] >= 93: 761 | goto tr9 762 | } 763 | default: 764 | goto tr9 765 | } 766 | goto tr2 767 | st18: 768 | if p++; p == pe { 769 | goto _test_eof18 770 | } 771 | st_case_18: 772 | switch data[p] { 773 | case 91: 774 | goto tr9 775 | case 96: 776 | goto tr9 777 | case 114: 778 | goto st19 779 | } 780 | switch { 781 | case data[p] < 58: 782 | if data[p] <= 47 { 783 | goto tr9 784 | } 785 | case data[p] > 64: 786 | switch { 787 | case data[p] > 94: 788 | if 123 <= data[p] && data[p] <= 127 { 789 | goto tr9 790 | } 791 | case data[p] >= 93: 792 | goto tr9 793 | } 794 | default: 795 | goto tr9 796 | } 797 | goto tr2 798 | st19: 799 | if p++; p == pe { 800 | goto _test_eof19 801 | } 802 | st_case_19: 803 | switch data[p] { 804 | case 91: 805 | goto tr9 806 | case 96: 807 | goto tr9 808 | case 105: 809 | goto st20 810 | } 811 | switch { 812 | case data[p] < 58: 813 | if data[p] <= 47 { 814 | goto tr9 815 | } 816 | case data[p] > 64: 817 | switch { 818 | case data[p] > 94: 819 | if 123 <= data[p] && data[p] <= 127 { 820 | goto tr9 821 | } 822 | case data[p] >= 93: 823 | goto tr9 824 | } 825 | default: 826 | goto tr9 827 | } 828 | goto tr2 829 | st20: 830 | if p++; p == pe { 831 | goto _test_eof20 832 | } 833 | st_case_20: 834 | switch data[p] { 835 | case 91: 836 | goto tr9 837 | case 96: 838 | goto tr9 839 | case 110: 840 | goto st21 841 | } 842 | switch { 843 | case data[p] < 58: 844 | if data[p] <= 47 { 845 | goto tr9 846 | } 847 | case data[p] > 64: 848 | switch { 849 | case data[p] > 94: 850 | if 123 <= data[p] && data[p] <= 127 { 851 | goto tr9 852 | } 853 | case data[p] >= 93: 854 | goto tr9 855 | } 856 | default: 857 | goto tr9 858 | } 859 | goto tr2 860 | st21: 861 | if p++; p == pe { 862 | goto _test_eof21 863 | } 864 | st_case_21: 865 | switch data[p] { 866 | case 91: 867 | goto tr9 868 | case 96: 869 | goto tr9 870 | case 103: 871 | goto tr30 872 | } 873 | switch { 874 | case data[p] < 58: 875 | if data[p] <= 47 { 876 | goto tr9 877 | } 878 | case data[p] > 64: 879 | switch { 880 | case data[p] > 94: 881 | if 123 <= data[p] && data[p] <= 127 { 882 | goto tr9 883 | } 884 | case data[p] >= 93: 885 | goto tr9 886 | } 887 | default: 888 | goto tr9 889 | } 890 | goto tr2 891 | st_out: 892 | _test_eof0: cs = 0; goto _test_eof 893 | _test_eof1: cs = 1; goto _test_eof 894 | _test_eof2: cs = 2; goto _test_eof 895 | _test_eof3: cs = 3; goto _test_eof 896 | _test_eof4: cs = 4; goto _test_eof 897 | _test_eof5: cs = 5; goto _test_eof 898 | _test_eof6: cs = 6; goto _test_eof 899 | _test_eof7: cs = 7; goto _test_eof 900 | _test_eof8: cs = 8; goto _test_eof 901 | _test_eof9: cs = 9; goto _test_eof 902 | _test_eof10: cs = 10; goto _test_eof 903 | _test_eof11: cs = 11; goto _test_eof 904 | _test_eof12: cs = 12; goto _test_eof 905 | _test_eof13: cs = 13; goto _test_eof 906 | _test_eof14: cs = 14; goto _test_eof 907 | _test_eof15: cs = 15; goto _test_eof 908 | _test_eof16: cs = 16; goto _test_eof 909 | _test_eof17: cs = 17; goto _test_eof 910 | _test_eof18: cs = 18; goto _test_eof 911 | _test_eof19: cs = 19; goto _test_eof 912 | _test_eof20: cs = 20; goto _test_eof 913 | _test_eof21: cs = 21; goto _test_eof 914 | 915 | _test_eof: {} 916 | if p == eof { 917 | switch cs { 918 | case 1: 919 | goto tr8 920 | case 2: 921 | goto tr9 922 | case 3: 923 | goto tr9 924 | case 4: 925 | goto tr9 926 | case 5: 927 | goto tr9 928 | case 6: 929 | goto tr9 930 | case 7: 931 | goto tr9 932 | case 8: 933 | goto tr9 934 | case 9: 935 | goto tr9 936 | case 10: 937 | goto tr9 938 | case 11: 939 | goto tr9 940 | case 12: 941 | goto tr9 942 | case 13: 943 | goto tr9 944 | case 14: 945 | goto tr9 946 | case 15: 947 | goto tr9 948 | case 16: 949 | goto tr9 950 | case 17: 951 | goto tr9 952 | case 18: 953 | goto tr9 954 | case 19: 955 | goto tr9 956 | case 20: 957 | goto tr9 958 | case 21: 959 | goto tr9 960 | } 961 | } 962 | 963 | _out: {} 964 | } 965 | 966 | //line phpdoc.rl:56 967 | 968 | l.pos = p 969 | if tok == T_NAME { 970 | lval.text = data[ts:te] 971 | } 972 | 973 | return tok 974 | } 975 | 976 | func (l *PhpdocLex) Error(s string) { 977 | fmt.Printf("syntax error: %s\n", s) 978 | } 979 | 980 | 981 | //line lexer.go:982 982 | const lexer_start int = 0 983 | const lexer_first_final int = 0 984 | const lexer_error int = -1 985 | 986 | const lexer_en_main int = 0 987 | 988 | 989 | //line phpdoc.rl:70 990 | -------------------------------------------------------------------------------- /yacc_ragel/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | flag.Parse() 10 | 11 | lexer := NewLexer() 12 | lexer.Init(flag.Args()[0]) 13 | parser := PhpdocParserImpl{} 14 | status := parser.Parse(lexer) 15 | fmt.Println(status) 16 | ast := lexer.result 17 | fmt.Printf("%#v\n", ast) 18 | fmt.Printf("%s\n", ast) 19 | } 20 | -------------------------------------------------------------------------------- /yacc_ragel/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/quasilyte/parsing-in-go/phpdoc" 7 | "github.com/quasilyte/parsing-in-go/phpdoctest" 8 | ) 9 | 10 | type parserWrapper struct { 11 | parser PhpdocParserImpl 12 | } 13 | 14 | func (p *parserWrapper) Parse(s string) (phpdoc.Type, error) { 15 | lexer := NewLexer() 16 | lexer.Init(s) 17 | p.parser.Parse(lexer) 18 | return lexer.result, nil 19 | } 20 | 21 | func TestMain(t *testing.T) { 22 | parser := &parserWrapper{} 23 | phpdoctest.Run(t, parser) 24 | } 25 | 26 | func BenchmarkParser(b *testing.B) { 27 | parser := &parserWrapper{} 28 | phpdoctest.RunBenchmark(b, parser) 29 | } 30 | -------------------------------------------------------------------------------- /yacc_ragel/phpdoc.rl: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | %%machine lexer; 4 | 5 | import ( 6 | "github.com/quasilyte/parsing-in-go/phpdoc" 7 | "fmt" 8 | ) 9 | 10 | type PhpdocLex struct { 11 | pos int 12 | src string 13 | result phpdoc.Type 14 | } 15 | 16 | func NewLexer() *PhpdocLex { 17 | return &PhpdocLex{} 18 | } 19 | 20 | func (l *PhpdocLex) Init(s string) { 21 | l.pos = 0 22 | l.src = s 23 | } 24 | 25 | func (l *PhpdocLex) Lex(lval *PhpdocSymType) int { 26 | tok := 0 27 | 28 | data := l.src 29 | cs, p, pe := 0, l.pos, len(data) 30 | eof := pe 31 | var ts, te int 32 | var act int 33 | 34 | %%{ 35 | whitespace = [\t ]; 36 | 37 | ident_first = [a-zA-Z_] | (0x0080..0x00FF); 38 | ident_rest = ident_first | [0-9] | [\\]; 39 | ident = ident_first (ident_rest)*; 40 | 41 | main := |* 42 | whitespace => {}; 43 | 'int' => { tok = T_INT; fbreak; }; 44 | 'float' => { tok = T_FLOAT; fbreak; }; 45 | 'null' => { tok = T_NULL; fbreak; }; 46 | 'string' => { tok = T_STRING; fbreak; }; 47 | 'false' => { tok = T_FALSE; fbreak; }; 48 | 'bool' => { tok = T_BOOL; fbreak; }; 49 | ident => { tok = T_NAME; fbreak; }; 50 | any => { tok = int(data[ts]); fbreak; }; 51 | *|; 52 | }%% 53 | 54 | %%write init; 55 | %%write exec; 56 | 57 | l.pos = p 58 | if tok == T_NAME { 59 | lval.text = data[ts:te] 60 | } 61 | 62 | return tok 63 | } 64 | 65 | func (l *PhpdocLex) Error(s string) { 66 | fmt.Printf("syntax error: %s\n", s) 67 | } 68 | 69 | %%write data; 70 | -------------------------------------------------------------------------------- /yacc_ragel/phpdoc.y: -------------------------------------------------------------------------------- 1 | %{ 2 | package main 3 | 4 | import ( 5 | "github.com/quasilyte/parsing-in-go/phpdoc" 6 | "strings" 7 | ) 8 | 9 | %} 10 | 11 | %union{ 12 | tok rune 13 | expr phpdoc.Type 14 | text string 15 | } 16 | 17 | %token T_NULL 18 | %token T_FALSE 19 | %token T_INT 20 | %token T_FLOAT 21 | %token T_STRING 22 | %token T_BOOL 23 | %token T_NAME 24 | 25 | %right '|' 26 | %right '&' 27 | %left OPTIONAL 28 | %right '[' 29 | %left '?' 30 | 31 | %type type_expr 32 | %type primitive_type 33 | 34 | %start start 35 | 36 | %% 37 | 38 | start 39 | : type_expr { Phpdoclex.(*PhpdocLex).result = $1 } 40 | ; 41 | 42 | type_expr 43 | : primitive_type { $$ = $1 } 44 | | T_NAME { $$ = &phpdoc.TypeName{Parts: strings.Split($1, `\`)} } 45 | | '(' type_expr ')' { $$ = $2 } 46 | | '?' type_expr { $$ = &phpdoc.NullableType{Elem: $2} } 47 | | type_expr '[' ']' { $$ = &phpdoc.ArrayType{Elem: $1} } 48 | | type_expr '?' %prec OPTIONAL { $$ = &phpdoc.OptionalKeyType{Elem: $1} } 49 | | type_expr '&' type_expr { $$ = &phpdoc.IntersectionType{X: $1, Y: $3} } 50 | | type_expr '|' type_expr { $$ = &phpdoc.UnionType{X: $1, Y: $3} } 51 | ; 52 | 53 | primitive_type 54 | : T_NULL { $$ = &phpdoc.PrimitiveTypeName{Name: "null"} } 55 | | T_FALSE { $$ = &phpdoc.PrimitiveTypeName{Name: "false"} } 56 | | T_INT { $$ = &phpdoc.PrimitiveTypeName{Name: "int"} } 57 | | T_FLOAT { $$ = &phpdoc.PrimitiveTypeName{Name: "float"} } 58 | | T_STRING { $$ = &phpdoc.PrimitiveTypeName{Name: "string"} } 59 | | T_BOOL { $$ = &phpdoc.PrimitiveTypeName{Name: "bool"} } 60 | ; 61 | 62 | %% 63 | -------------------------------------------------------------------------------- /yacc_ragel/y.go: -------------------------------------------------------------------------------- 1 | // Code generated by goyacc -p Phpdoc phpdoc.y. DO NOT EDIT. 2 | 3 | //line phpdoc.y:2 4 | package main 5 | 6 | import __yyfmt__ "fmt" 7 | 8 | //line phpdoc.y:2 9 | 10 | import ( 11 | "github.com/quasilyte/parsing-in-go/phpdoc" 12 | "strings" 13 | ) 14 | 15 | //line phpdoc.y:11 16 | type PhpdocSymType struct { 17 | yys int 18 | tok rune 19 | expr phpdoc.Type 20 | text string 21 | } 22 | 23 | const T_NULL = 57346 24 | const T_FALSE = 57347 25 | const T_INT = 57348 26 | const T_FLOAT = 57349 27 | const T_STRING = 57350 28 | const T_BOOL = 57351 29 | const T_NAME = 57352 30 | const OPTIONAL = 57353 31 | 32 | var PhpdocToknames = [...]string{ 33 | "$end", 34 | "error", 35 | "$unk", 36 | "T_NULL", 37 | "T_FALSE", 38 | "T_INT", 39 | "T_FLOAT", 40 | "T_STRING", 41 | "T_BOOL", 42 | "T_NAME", 43 | "'|'", 44 | "'&'", 45 | "OPTIONAL", 46 | "'['", 47 | "'?'", 48 | "'('", 49 | "')'", 50 | "']'", 51 | } 52 | var PhpdocStatenames = [...]string{} 53 | 54 | const PhpdocEofCode = 1 55 | const PhpdocErrCode = 2 56 | const PhpdocInitialStackSize = 16 57 | 58 | //line phpdoc.y:62 59 | 60 | //line yacctab:1 61 | var PhpdocExca = [...]int{ 62 | -1, 1, 63 | 1, -1, 64 | -2, 0, 65 | } 66 | 67 | const PhpdocPrivate = 57344 68 | 69 | const PhpdocLast = 37 70 | 71 | var PhpdocAct = [...]int{ 72 | 73 | 7, 8, 9, 10, 11, 12, 4, 19, 1, 3, 74 | 0, 6, 5, 16, 15, 2, 13, 14, 0, 22, 75 | 0, 17, 18, 16, 15, 0, 13, 14, 0, 0, 76 | 0, 20, 21, 15, 0, 13, 14, 77 | } 78 | var PhpdocPact = [...]int{ 79 | 80 | -4, -1000, 12, -1000, -1000, -4, -4, -1000, -1000, -1000, 81 | -1000, -1000, -1000, -11, -1000, -4, -4, 2, -1000, -1000, 82 | 21, 12, -1000, 83 | } 84 | var PhpdocPgo = [...]int{ 85 | 86 | 0, 15, 9, 8, 87 | } 88 | var PhpdocR1 = [...]int{ 89 | 90 | 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 91 | 2, 2, 2, 2, 2, 2, 92 | } 93 | var PhpdocR2 = [...]int{ 94 | 95 | 0, 1, 1, 1, 3, 2, 3, 2, 3, 3, 96 | 1, 1, 1, 1, 1, 1, 97 | } 98 | var PhpdocChk = [...]int{ 99 | 100 | -1000, -3, -1, -2, 10, 16, 15, 4, 5, 6, 101 | 7, 8, 9, 14, 15, 12, 11, -1, -1, 18, 102 | -1, -1, 17, 103 | } 104 | var PhpdocDef = [...]int{ 105 | 106 | 0, -2, 1, 2, 3, 0, 0, 10, 11, 12, 107 | 13, 14, 15, 0, 7, 0, 0, 0, 5, 6, 108 | 8, 9, 4, 109 | } 110 | var PhpdocTok1 = [...]int{ 111 | 112 | 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 113 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 114 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 115 | 3, 3, 3, 3, 3, 3, 3, 3, 12, 3, 116 | 16, 17, 3, 3, 3, 3, 3, 3, 3, 3, 117 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 118 | 3, 3, 3, 15, 3, 3, 3, 3, 3, 3, 119 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 120 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 121 | 3, 14, 3, 18, 3, 3, 3, 3, 3, 3, 122 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 123 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 124 | 3, 3, 3, 3, 11, 125 | } 126 | var PhpdocTok2 = [...]int{ 127 | 128 | 2, 3, 4, 5, 6, 7, 8, 9, 10, 13, 129 | } 130 | var PhpdocTok3 = [...]int{ 131 | 0, 132 | } 133 | 134 | var PhpdocErrorMessages = [...]struct { 135 | state int 136 | token int 137 | msg string 138 | }{} 139 | 140 | //line yaccpar:1 141 | 142 | /* parser for yacc output */ 143 | 144 | var ( 145 | PhpdocDebug = 0 146 | PhpdocErrorVerbose = false 147 | ) 148 | 149 | type PhpdocLexer interface { 150 | Lex(lval *PhpdocSymType) int 151 | Error(s string) 152 | } 153 | 154 | type PhpdocParser interface { 155 | Parse(PhpdocLexer) int 156 | Lookahead() int 157 | } 158 | 159 | type PhpdocParserImpl struct { 160 | lval PhpdocSymType 161 | stack [PhpdocInitialStackSize]PhpdocSymType 162 | char int 163 | } 164 | 165 | func (p *PhpdocParserImpl) Lookahead() int { 166 | return p.char 167 | } 168 | 169 | func PhpdocNewParser() PhpdocParser { 170 | return &PhpdocParserImpl{} 171 | } 172 | 173 | const PhpdocFlag = -1000 174 | 175 | func PhpdocTokname(c int) string { 176 | if c >= 1 && c-1 < len(PhpdocToknames) { 177 | if PhpdocToknames[c-1] != "" { 178 | return PhpdocToknames[c-1] 179 | } 180 | } 181 | return __yyfmt__.Sprintf("tok-%v", c) 182 | } 183 | 184 | func PhpdocStatname(s int) string { 185 | if s >= 0 && s < len(PhpdocStatenames) { 186 | if PhpdocStatenames[s] != "" { 187 | return PhpdocStatenames[s] 188 | } 189 | } 190 | return __yyfmt__.Sprintf("state-%v", s) 191 | } 192 | 193 | func PhpdocErrorMessage(state, lookAhead int) string { 194 | const TOKSTART = 4 195 | 196 | if !PhpdocErrorVerbose { 197 | return "syntax error" 198 | } 199 | 200 | for _, e := range PhpdocErrorMessages { 201 | if e.state == state && e.token == lookAhead { 202 | return "syntax error: " + e.msg 203 | } 204 | } 205 | 206 | res := "syntax error: unexpected " + PhpdocTokname(lookAhead) 207 | 208 | // To match Bison, suggest at most four expected tokens. 209 | expected := make([]int, 0, 4) 210 | 211 | // Look for shiftable tokens. 212 | base := PhpdocPact[state] 213 | for tok := TOKSTART; tok-1 < len(PhpdocToknames); tok++ { 214 | if n := base + tok; n >= 0 && n < PhpdocLast && PhpdocChk[PhpdocAct[n]] == tok { 215 | if len(expected) == cap(expected) { 216 | return res 217 | } 218 | expected = append(expected, tok) 219 | } 220 | } 221 | 222 | if PhpdocDef[state] == -2 { 223 | i := 0 224 | for PhpdocExca[i] != -1 || PhpdocExca[i+1] != state { 225 | i += 2 226 | } 227 | 228 | // Look for tokens that we accept or reduce. 229 | for i += 2; PhpdocExca[i] >= 0; i += 2 { 230 | tok := PhpdocExca[i] 231 | if tok < TOKSTART || PhpdocExca[i+1] == 0 { 232 | continue 233 | } 234 | if len(expected) == cap(expected) { 235 | return res 236 | } 237 | expected = append(expected, tok) 238 | } 239 | 240 | // If the default action is to accept or reduce, give up. 241 | if PhpdocExca[i+1] != 0 { 242 | return res 243 | } 244 | } 245 | 246 | for i, tok := range expected { 247 | if i == 0 { 248 | res += ", expecting " 249 | } else { 250 | res += " or " 251 | } 252 | res += PhpdocTokname(tok) 253 | } 254 | return res 255 | } 256 | 257 | func Phpdoclex1(lex PhpdocLexer, lval *PhpdocSymType) (char, token int) { 258 | token = 0 259 | char = lex.Lex(lval) 260 | if char <= 0 { 261 | token = PhpdocTok1[0] 262 | goto out 263 | } 264 | if char < len(PhpdocTok1) { 265 | token = PhpdocTok1[char] 266 | goto out 267 | } 268 | if char >= PhpdocPrivate { 269 | if char < PhpdocPrivate+len(PhpdocTok2) { 270 | token = PhpdocTok2[char-PhpdocPrivate] 271 | goto out 272 | } 273 | } 274 | for i := 0; i < len(PhpdocTok3); i += 2 { 275 | token = PhpdocTok3[i+0] 276 | if token == char { 277 | token = PhpdocTok3[i+1] 278 | goto out 279 | } 280 | } 281 | 282 | out: 283 | if token == 0 { 284 | token = PhpdocTok2[1] /* unknown char */ 285 | } 286 | if PhpdocDebug >= 3 { 287 | __yyfmt__.Printf("lex %s(%d)\n", PhpdocTokname(token), uint(char)) 288 | } 289 | return char, token 290 | } 291 | 292 | func PhpdocParse(Phpdoclex PhpdocLexer) int { 293 | return PhpdocNewParser().Parse(Phpdoclex) 294 | } 295 | 296 | func (Phpdocrcvr *PhpdocParserImpl) Parse(Phpdoclex PhpdocLexer) int { 297 | var Phpdocn int 298 | var PhpdocVAL PhpdocSymType 299 | var PhpdocDollar []PhpdocSymType 300 | _ = PhpdocDollar // silence set and not used 301 | PhpdocS := Phpdocrcvr.stack[:] 302 | 303 | Nerrs := 0 /* number of errors */ 304 | Errflag := 0 /* error recovery flag */ 305 | Phpdocstate := 0 306 | Phpdocrcvr.char = -1 307 | Phpdoctoken := -1 // Phpdocrcvr.char translated into internal numbering 308 | defer func() { 309 | // Make sure we report no lookahead when not parsing. 310 | Phpdocstate = -1 311 | Phpdocrcvr.char = -1 312 | Phpdoctoken = -1 313 | }() 314 | Phpdocp := -1 315 | goto Phpdocstack 316 | 317 | ret0: 318 | return 0 319 | 320 | ret1: 321 | return 1 322 | 323 | Phpdocstack: 324 | /* put a state and value onto the stack */ 325 | if PhpdocDebug >= 4 { 326 | __yyfmt__.Printf("char %v in %v\n", PhpdocTokname(Phpdoctoken), PhpdocStatname(Phpdocstate)) 327 | } 328 | 329 | Phpdocp++ 330 | if Phpdocp >= len(PhpdocS) { 331 | nyys := make([]PhpdocSymType, len(PhpdocS)*2) 332 | copy(nyys, PhpdocS) 333 | PhpdocS = nyys 334 | } 335 | PhpdocS[Phpdocp] = PhpdocVAL 336 | PhpdocS[Phpdocp].yys = Phpdocstate 337 | 338 | Phpdocnewstate: 339 | Phpdocn = PhpdocPact[Phpdocstate] 340 | if Phpdocn <= PhpdocFlag { 341 | goto Phpdocdefault /* simple state */ 342 | } 343 | if Phpdocrcvr.char < 0 { 344 | Phpdocrcvr.char, Phpdoctoken = Phpdoclex1(Phpdoclex, &Phpdocrcvr.lval) 345 | } 346 | Phpdocn += Phpdoctoken 347 | if Phpdocn < 0 || Phpdocn >= PhpdocLast { 348 | goto Phpdocdefault 349 | } 350 | Phpdocn = PhpdocAct[Phpdocn] 351 | if PhpdocChk[Phpdocn] == Phpdoctoken { /* valid shift */ 352 | Phpdocrcvr.char = -1 353 | Phpdoctoken = -1 354 | PhpdocVAL = Phpdocrcvr.lval 355 | Phpdocstate = Phpdocn 356 | if Errflag > 0 { 357 | Errflag-- 358 | } 359 | goto Phpdocstack 360 | } 361 | 362 | Phpdocdefault: 363 | /* default state action */ 364 | Phpdocn = PhpdocDef[Phpdocstate] 365 | if Phpdocn == -2 { 366 | if Phpdocrcvr.char < 0 { 367 | Phpdocrcvr.char, Phpdoctoken = Phpdoclex1(Phpdoclex, &Phpdocrcvr.lval) 368 | } 369 | 370 | /* look through exception table */ 371 | xi := 0 372 | for { 373 | if PhpdocExca[xi+0] == -1 && PhpdocExca[xi+1] == Phpdocstate { 374 | break 375 | } 376 | xi += 2 377 | } 378 | for xi += 2; ; xi += 2 { 379 | Phpdocn = PhpdocExca[xi+0] 380 | if Phpdocn < 0 || Phpdocn == Phpdoctoken { 381 | break 382 | } 383 | } 384 | Phpdocn = PhpdocExca[xi+1] 385 | if Phpdocn < 0 { 386 | goto ret0 387 | } 388 | } 389 | if Phpdocn == 0 { 390 | /* error ... attempt to resume parsing */ 391 | switch Errflag { 392 | case 0: /* brand new error */ 393 | Phpdoclex.Error(PhpdocErrorMessage(Phpdocstate, Phpdoctoken)) 394 | Nerrs++ 395 | if PhpdocDebug >= 1 { 396 | __yyfmt__.Printf("%s", PhpdocStatname(Phpdocstate)) 397 | __yyfmt__.Printf(" saw %s\n", PhpdocTokname(Phpdoctoken)) 398 | } 399 | fallthrough 400 | 401 | case 1, 2: /* incompletely recovered error ... try again */ 402 | Errflag = 3 403 | 404 | /* find a state where "error" is a legal shift action */ 405 | for Phpdocp >= 0 { 406 | Phpdocn = PhpdocPact[PhpdocS[Phpdocp].yys] + PhpdocErrCode 407 | if Phpdocn >= 0 && Phpdocn < PhpdocLast { 408 | Phpdocstate = PhpdocAct[Phpdocn] /* simulate a shift of "error" */ 409 | if PhpdocChk[Phpdocstate] == PhpdocErrCode { 410 | goto Phpdocstack 411 | } 412 | } 413 | 414 | /* the current p has no shift on "error", pop stack */ 415 | if PhpdocDebug >= 2 { 416 | __yyfmt__.Printf("error recovery pops state %d\n", PhpdocS[Phpdocp].yys) 417 | } 418 | Phpdocp-- 419 | } 420 | /* there is no state on the stack with an error shift ... abort */ 421 | goto ret1 422 | 423 | case 3: /* no shift yet; clobber input char */ 424 | if PhpdocDebug >= 2 { 425 | __yyfmt__.Printf("error recovery discards %s\n", PhpdocTokname(Phpdoctoken)) 426 | } 427 | if Phpdoctoken == PhpdocEofCode { 428 | goto ret1 429 | } 430 | Phpdocrcvr.char = -1 431 | Phpdoctoken = -1 432 | goto Phpdocnewstate /* try again in the same state */ 433 | } 434 | } 435 | 436 | /* reduction by production Phpdocn */ 437 | if PhpdocDebug >= 2 { 438 | __yyfmt__.Printf("reduce %v in:\n\t%v\n", Phpdocn, PhpdocStatname(Phpdocstate)) 439 | } 440 | 441 | Phpdocnt := Phpdocn 442 | Phpdocpt := Phpdocp 443 | _ = Phpdocpt // guard against "declared and not used" 444 | 445 | Phpdocp -= PhpdocR2[Phpdocn] 446 | // Phpdocp is now the index of $0. Perform the default action. Iff the 447 | // reduced production is ε, $1 is possibly out of range. 448 | if Phpdocp+1 >= len(PhpdocS) { 449 | nyys := make([]PhpdocSymType, len(PhpdocS)*2) 450 | copy(nyys, PhpdocS) 451 | PhpdocS = nyys 452 | } 453 | PhpdocVAL = PhpdocS[Phpdocp+1] 454 | 455 | /* consult goto table to find next state */ 456 | Phpdocn = PhpdocR1[Phpdocn] 457 | Phpdocg := PhpdocPgo[Phpdocn] 458 | Phpdocj := Phpdocg + PhpdocS[Phpdocp].yys + 1 459 | 460 | if Phpdocj >= PhpdocLast { 461 | Phpdocstate = PhpdocAct[Phpdocg] 462 | } else { 463 | Phpdocstate = PhpdocAct[Phpdocj] 464 | if PhpdocChk[Phpdocstate] != -Phpdocn { 465 | Phpdocstate = PhpdocAct[Phpdocg] 466 | } 467 | } 468 | // dummy call; replaced with literal code 469 | switch Phpdocnt { 470 | 471 | case 1: 472 | PhpdocDollar = PhpdocS[Phpdocpt-1 : Phpdocpt+1] 473 | //line phpdoc.y:39 474 | { 475 | Phpdoclex.(*PhpdocLex).result = PhpdocDollar[1].expr 476 | } 477 | case 2: 478 | PhpdocDollar = PhpdocS[Phpdocpt-1 : Phpdocpt+1] 479 | //line phpdoc.y:43 480 | { 481 | PhpdocVAL.expr = PhpdocDollar[1].expr 482 | } 483 | case 3: 484 | PhpdocDollar = PhpdocS[Phpdocpt-1 : Phpdocpt+1] 485 | //line phpdoc.y:44 486 | { 487 | PhpdocVAL.expr = &phpdoc.TypeName{Parts: strings.Split(PhpdocDollar[1].text, `\`)} 488 | } 489 | case 4: 490 | PhpdocDollar = PhpdocS[Phpdocpt-3 : Phpdocpt+1] 491 | //line phpdoc.y:45 492 | { 493 | PhpdocVAL.expr = PhpdocDollar[2].expr 494 | } 495 | case 5: 496 | PhpdocDollar = PhpdocS[Phpdocpt-2 : Phpdocpt+1] 497 | //line phpdoc.y:46 498 | { 499 | PhpdocVAL.expr = &phpdoc.NullableType{Elem: PhpdocDollar[2].expr} 500 | } 501 | case 6: 502 | PhpdocDollar = PhpdocS[Phpdocpt-3 : Phpdocpt+1] 503 | //line phpdoc.y:47 504 | { 505 | PhpdocVAL.expr = &phpdoc.ArrayType{Elem: PhpdocDollar[1].expr} 506 | } 507 | case 7: 508 | PhpdocDollar = PhpdocS[Phpdocpt-2 : Phpdocpt+1] 509 | //line phpdoc.y:48 510 | { 511 | PhpdocVAL.expr = &phpdoc.OptionalKeyType{Elem: PhpdocDollar[1].expr} 512 | } 513 | case 8: 514 | PhpdocDollar = PhpdocS[Phpdocpt-3 : Phpdocpt+1] 515 | //line phpdoc.y:49 516 | { 517 | PhpdocVAL.expr = &phpdoc.IntersectionType{X: PhpdocDollar[1].expr, Y: PhpdocDollar[3].expr} 518 | } 519 | case 9: 520 | PhpdocDollar = PhpdocS[Phpdocpt-3 : Phpdocpt+1] 521 | //line phpdoc.y:50 522 | { 523 | PhpdocVAL.expr = &phpdoc.UnionType{X: PhpdocDollar[1].expr, Y: PhpdocDollar[3].expr} 524 | } 525 | case 10: 526 | PhpdocDollar = PhpdocS[Phpdocpt-1 : Phpdocpt+1] 527 | //line phpdoc.y:54 528 | { 529 | PhpdocVAL.expr = &phpdoc.PrimitiveTypeName{Name: "null"} 530 | } 531 | case 11: 532 | PhpdocDollar = PhpdocS[Phpdocpt-1 : Phpdocpt+1] 533 | //line phpdoc.y:55 534 | { 535 | PhpdocVAL.expr = &phpdoc.PrimitiveTypeName{Name: "false"} 536 | } 537 | case 12: 538 | PhpdocDollar = PhpdocS[Phpdocpt-1 : Phpdocpt+1] 539 | //line phpdoc.y:56 540 | { 541 | PhpdocVAL.expr = &phpdoc.PrimitiveTypeName{Name: "int"} 542 | } 543 | case 13: 544 | PhpdocDollar = PhpdocS[Phpdocpt-1 : Phpdocpt+1] 545 | //line phpdoc.y:57 546 | { 547 | PhpdocVAL.expr = &phpdoc.PrimitiveTypeName{Name: "float"} 548 | } 549 | case 14: 550 | PhpdocDollar = PhpdocS[Phpdocpt-1 : Phpdocpt+1] 551 | //line phpdoc.y:58 552 | { 553 | PhpdocVAL.expr = &phpdoc.PrimitiveTypeName{Name: "string"} 554 | } 555 | case 15: 556 | PhpdocDollar = PhpdocS[Phpdocpt-1 : Phpdocpt+1] 557 | //line phpdoc.y:59 558 | { 559 | PhpdocVAL.expr = &phpdoc.PrimitiveTypeName{Name: "bool"} 560 | } 561 | } 562 | goto Phpdocstack /* stack new state and value */ 563 | } 564 | --------------------------------------------------------------------------------