// Copyright 2017 syzkaller project authors. All rights reserved. // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. package ast import ( "errors" "fmt" "io/ioutil" "path/filepath" "strconv" "strings" ) // Parse parses sys description into AST and returns top-level nodes. // If any errors are encountered, returns nil. func Parse(data []byte, filename string, errorHandler ErrorHandler) *Description { p := &parser{s: newScanner(data, filename, errorHandler)} prevNewLine, prevComment := false, false var top []Node for p.next(); p.tok != tokEOF; { decl := p.parseTopRecover() if decl == nil { continue } // Add new lines around structs, remove duplicate new lines. if _, ok := decl.(*NewLine); ok && prevNewLine { continue } if str, ok := decl.(*Struct); ok && !prevNewLine && !prevComment { top = append(top, &NewLine{Pos: str.Pos}) } top = append(top, decl) if str, ok := decl.(*Struct); ok { decl = &NewLine{Pos: str.Pos} top = append(top, decl) } _, prevNewLine = decl.(*NewLine) _, prevComment = decl.(*Comment) } if prevNewLine { top = top[:len(top)-1] } if !p.s.Ok() { return nil } return &Description{top} } func ParseGlob(glob string, errorHandler ErrorHandler) *Description { if errorHandler == nil { errorHandler = LoggingHandler } files, err := filepath.Glob(glob) if err != nil { errorHandler(Pos{}, fmt.Sprintf("failed to find input files: %v", err)) return nil } if len(files) == 0 { errorHandler(Pos{}, fmt.Sprintf("no files matched by glob %q", glob)) return nil } desc := &Description{} for _, f := range files { data, err := ioutil.ReadFile(f) if err != nil { errorHandler(Pos{}, fmt.Sprintf("failed to read input file: %v", err)) return nil } desc1 := Parse(data, filepath.Base(f), errorHandler) if desc1 == nil { desc = nil } if desc != nil { desc.Nodes = append(desc.Nodes, desc1.Nodes...) } } return desc } type parser struct { s *scanner // Current token: tok token lit string pos Pos } // Skip parsing till the next NEWLINE, for error recovery. var errSkipLine = errors.New("") func (p *parser) parseTopRecover() Node { defer func() { switch err := recover(); err { case nil: case errSkipLine: // Try to recover by consuming everything until next NEWLINE. for p.tok != tokNewLine && p.tok != tokEOF { p.next() } p.tryConsume(tokNewLine) default: panic(err) } }() decl := p.parseTop() if decl == nil { panic("not reachable") } p.consume(tokNewLine) return decl } func (p *parser) parseTop() Node { switch p.tok { case tokNewLine: return &NewLine{Pos: p.pos} case tokComment: return p.parseComment() case tokDefine: return p.parseDefine() case tokInclude: return p.parseInclude() case tokIncdir: return p.parseIncdir() case tokResource: return p.parseResource() case tokIdent: name := p.parseIdent() if name.Name == "type" { return p.parseTypeDef() } switch p.tok { case tokLParen: return p.parseCall(name) case tokLBrace, tokLBrack: return p.parseStruct(name) case tokEq: return p.parseFlags(name) default: p.expect(tokLParen, tokLBrace, tokLBrack, tokEq) } case tokIllegal: // Scanner has already producer an error for this one. panic(errSkipLine) default: p.expect(tokComment, tokDefine, tokInclude, tokResource, tokIdent) } panic("not reachable") } func (p *parser) next() { p.tok, p.lit, p.pos = p.s.Scan() } func (p *parser) consume(tok token) { p.expect(tok) p.next() } func (p *parser) tryConsume(tok token) bool { if p.tok != tok { return false } p.next() return true } func (p *parser) expect(tokens ...token) { for _, tok := range tokens { if p.tok == tok { return } } var str []string for _, tok := range tokens { str = append(str, tok.String()) } p.s.Error(p.pos, fmt.Sprintf("unexpected %v, expecting %v", p.tok, strings.Join(str, ", "))) panic(errSkipLine) } func (p *parser) parseComment() *Comment { c := &Comment{ Pos: p.pos, Text: p.lit, } p.consume(tokComment) return c } func (p *parser) parseDefine() *Define { pos0 := p.pos p.consume(tokDefine) name := p.parseIdent() p.expect(tokInt, tokIdent, tokCExpr) var val *Int if p.tok == tokCExpr { val = p.parseCExpr() } else { val = p.parseInt() } return &Define{ Pos: pos0, Name: name, Value: val, } } func (p *parser) parseInclude() *Include { pos0 := p.pos p.consume(tokInclude) return &Include{ Pos: pos0, File: p.parseString(), } } func (p *parser) parseIncdir() *Incdir { pos0 := p.pos p.consume(tokIncdir) return &Incdir{ Pos: pos0, Dir: p.parseString(), } } func (p *parser) parseResource() *Resource { pos0 := p.pos p.consume(tokResource) name := p.parseIdent() p.consume(tokLBrack) base := p.parseType() p.consume(tokRBrack) var values []*Int if p.tryConsume(tokColon) { values = append(values, p.parseInt()) for p.tryConsume(tokComma) { values = append(values, p.parseInt()) } } return &Resource{ Pos: pos0, Name: name, Base: base, Values: values, } } func (p *parser) parseTypeDef() *TypeDef { pos0 := p.pos name := p.parseIdent() var typ *Type var str *Struct var args []*Ident p.expect(tokLBrack, tokIdent) if p.tryConsume(tokLBrack) { args = append(args, p.parseIdent()) for p.tryConsume(tokComma) { args = append(args, p.parseIdent()) } p.consume(tokRBrack) if p.tok == tokLBrace || p.tok == tokLBrack { emptyName := &Ident{ Pos: pos0, Name: "", } str = p.parseStruct(emptyName) } else { typ = p.parseType() } } else { typ = p.parseType() } return &TypeDef{ Pos: pos0, Name: name, Args: args, Type: typ, Struct: str, } } func (p *parser) parseCall(name *Ident) *Call { c := &Call{ Pos: name.Pos, Name: name, CallName: callName(name.Name), } p.consume(tokLParen) for p.tok != tokRParen { c.Args = append(c.Args, p.parseField()) p.expect(tokComma, tokRParen) p.tryConsume(tokComma) } p.consume(tokRParen) if p.tok != tokNewLine { c.Ret = p.parseType() } return c } func callName(s string) string { pos := strings.IndexByte(s, '$') if pos == -1 { return s } return s[:pos] } func (p *parser) parseFlags(name *Ident) Node { p.consume(tokEq) switch p.tok { case tokInt, tokIdent: return p.parseIntFlags(name) case tokString: return p.parseStrFlags(name) default: p.expect(tokInt, tokIdent, tokString) return nil } } func (p *parser) parseIntFlags(name *Ident) *IntFlags { values := []*Int{p.parseInt()} for p.tryConsume(tokComma) { values = append(values, p.parseInt()) } return &IntFlags{ Pos: name.Pos, Name: name, Values: values, } } func (p *parser) parseStrFlags(name *Ident) *StrFlags { values := []*String{p.parseString()} for p.tryConsume(tokComma) { values = append(values, p.parseString()) } return &StrFlags{ Pos: name.Pos, Name: name, Values: values, } } func (p *parser) parseStruct(name *Ident) *Struct { str := &Struct{ Pos: name.Pos, Name: name, } closing := tokRBrace if p.tok == tokLBrack { str.IsUnion = true closing = tokRBrack } p.next() p.consume(tokNewLine) for { newBlock := false for p.tok == tokNewLine { newBlock = true p.next() } comments := p.parseCommentBlock() if p.tryConsume(closing) { str.Comments = comments break } fld := p.parseField() fld.NewBlock = newBlock fld.Comments = comments str.Fields = append(str.Fields, fld) p.consume(tokNewLine) } if p.tryConsume(tokLBrack) { str.Attrs = append(str.Attrs, p.parseType()) for p.tryConsume(tokComma) { str.Attrs = append(str.Attrs, p.parseType()) } p.consume(tokRBrack) } return str } func (p *parser) parseCommentBlock() []*Comment { var comments []*Comment for p.tok == tokComment { comments = append(comments, p.parseComment()) p.consume(tokNewLine) for p.tryConsume(tokNewLine) { } } return comments } func (p *parser) parseField() *Field { name := p.parseIdent() return &Field{ Pos: name.Pos, Name: name, Type: p.parseType(), } } func (p *parser) parseType() *Type { arg := &Type{ Pos: p.pos, } allowColon := false switch p.tok { case tokInt: allowColon = true arg.Value, arg.ValueFmt = p.parseIntValue() case tokIdent: allowColon = true arg.Ident = p.lit case tokString: arg.String = p.lit arg.HasString = true default: p.expect(tokInt, tokIdent, tokString) } p.next() if allowColon && p.tryConsume(tokColon) { arg.HasColon = true arg.Pos2 = p.pos switch p.tok { case tokInt: arg.Value2, arg.Value2Fmt = p.parseIntValue() case tokIdent: arg.Ident2 = p.lit default: p.expect(tokInt, tokIdent) } p.next() } arg.Args = p.parseTypeList() return arg } func (p *parser) parseTypeList() []*Type { var args []*Type if p.tryConsume(tokLBrack) { args = append(args, p.parseType()) for p.tryConsume(tokComma) { args = append(args, p.parseType()) } p.consume(tokRBrack) } return args } func (p *parser) parseIdent() *Ident { p.expect(tokIdent) ident := &Ident{ Pos: p.pos, Name: p.lit, } p.next() return ident } func (p *parser) parseString() *String { p.expect(tokString) str := &String{ Pos: p.pos, Value: p.lit, } p.next() return str } func (p *parser) parseInt() *Int { i := &Int{ Pos: p.pos, } switch p.tok { case tokInt: i.Value, i.ValueFmt = p.parseIntValue() case tokIdent: i.Ident = p.lit default: p.expect(tokInt, tokIdent) } p.next() return i } func (p *parser) parseIntValue() (uint64, IntFmt) { if p.lit[0] == '\'' { return uint64(p.lit[1]), IntFmtChar } if v, err := strconv.ParseUint(p.lit, 10, 64); err == nil { return v, IntFmtDec } if v, err := strconv.ParseInt(p.lit, 10, 64); err == nil { return uint64(v), IntFmtNeg } if len(p.lit) > 2 && p.lit[0] == '0' && p.lit[1] == 'x' { if v, err := strconv.ParseUint(p.lit[2:], 16, 64); err == nil { return v, IntFmtHex } } panic(fmt.Sprintf("scanner returned bad integer %q", p.lit)) } func (p *parser) parseCExpr() *Int { i := &Int{ Pos: p.pos, CExpr: p.lit, } p.consume(tokCExpr) return i }