github.phpd.cn/thought-machine/please@v12.2.0+incompatible/src/parse/asp/parser.go (about) 1 // Package asp implements an experimental BUILD-language parser. 2 // Parsing is doing using Participle (github.com/alecthomas/participle) in native Go, 3 // with a custom and also native partial Python interpreter. 4 package asp 5 6 import ( 7 "bytes" 8 "encoding/gob" 9 "io" 10 "os" 11 "reflect" 12 "strings" 13 14 "gopkg.in/op/go-logging.v1" 15 16 "core" 17 ) 18 19 var log = logging.MustGetLogger("asp") 20 21 func init() { 22 // gob needs to know how to encode and decode our types. 23 gob.Register(None) 24 gob.Register(pyInt(0)) 25 gob.Register(pyString("")) 26 gob.Register(pyList{}) 27 gob.Register(pyDict{}) 28 } 29 30 // A Parser implements parsing of BUILD files. 31 type Parser struct { 32 interpreter *interpreter 33 // Stashed set of source code for builtin rules. 34 builtins map[string][]byte 35 } 36 37 // NewParser creates a new parser instance. One is normally sufficient for a process lifetime. 38 func NewParser(state *core.BuildState) *Parser { 39 p := newParser() 40 p.interpreter = newInterpreter(state, p) 41 return p 42 } 43 44 // newParser creates just the parser with no interpreter. 45 func newParser() *Parser { 46 return &Parser{builtins: map[string][]byte{}} 47 } 48 49 // LoadBuiltins instructs the parser to load rules from this file as built-ins. 50 // Optionally the file contents can be supplied directly. 51 // Also optionally a previously parsed form (acquired from ParseToFile) can be supplied. 52 func (p *Parser) LoadBuiltins(filename string, contents, encoded []byte) error { 53 var statements []*Statement 54 if len(encoded) != 0 { 55 decoder := gob.NewDecoder(bytes.NewReader(encoded)) 56 if err := decoder.Decode(&statements); err != nil { 57 log.Fatalf("Failed to decode pre-parsed rules: %s", err) 58 } 59 } 60 if len(contents) != 0 { 61 p.builtins[filename] = contents 62 } 63 if err := p.interpreter.LoadBuiltins(filename, contents, statements); err != nil { 64 return p.annotate(err, nil) 65 } 66 return nil 67 } 68 69 // MustLoadBuiltins calls LoadBuiltins, and dies on any errors. 70 func (p *Parser) MustLoadBuiltins(filename string, contents, encoded []byte) { 71 if err := p.LoadBuiltins(filename, contents, encoded); err != nil { 72 log.Fatalf("Error loading builtin rules: %s", err) 73 } 74 } 75 76 // ParseFile parses the contents of a single file in the BUILD language. 77 // It returns true if the call was deferred at some point awaiting target to build, 78 // along with any error encountered. 79 func (p *Parser) ParseFile(pkg *core.Package, filename string) error { 80 statements, err := p.parse(filename) 81 if err != nil { 82 return err 83 } 84 _, err = p.interpreter.interpretAll(pkg, statements) 85 if err != nil { 86 f, _ := os.Open(filename) 87 p.annotate(err, f) 88 } 89 return err 90 } 91 92 // ParseReader parses the contents of the given ReadSeeker as a BUILD file. 93 // This is provided as a helper for fuzzing and isn't generally useful otherwise. 94 // The first return value is true if parsing succeeds - if the error is still non-nil 95 // that indicates that interpretation failed. 96 func (p *Parser) ParseReader(pkg *core.Package, r io.ReadSeeker) (bool, error) { 97 stmts, err := p.parseAndHandleErrors(r, "") 98 if err != nil { 99 return false, err 100 } 101 _, err = p.interpreter.interpretAll(pkg, stmts) 102 return true, err 103 } 104 105 // ParseToFile parses the given file and writes a binary form of the result to the output file. 106 func (p *Parser) ParseToFile(input, output string) error { 107 stmts, err := p.parse(input) 108 if err != nil { 109 return err 110 } 111 stmts = p.optimise(stmts) 112 p.interpreter.optimiseExpressions(reflect.ValueOf(stmts)) 113 for _, stmt := range stmts { 114 if stmt.FuncDef != nil { 115 stmt.FuncDef.KeywordsOnly = !whitelistedKwargs(stmt.FuncDef.Name, input) 116 } 117 } 118 f, err := os.Create(output) 119 if err != nil { 120 return err 121 } 122 encoder := gob.NewEncoder(f) 123 if err := encoder.Encode(stmts); err != nil { 124 return err 125 } 126 return f.Close() 127 } 128 129 // ParseFileOnly parses the given file but does not interpret it. 130 func (p *Parser) ParseFileOnly(filename string) ([]*Statement, error) { 131 return p.parse(filename) 132 } 133 134 // parse reads the given file and parses it into a set of statements. 135 func (p *Parser) parse(filename string) ([]*Statement, error) { 136 f, err := os.Open(filename) 137 if err != nil { 138 return nil, err 139 } 140 stmts, err := p.parseAndHandleErrors(f, filename) 141 if err == nil { 142 // This appears a bit weird, but the error will still use the file if it's open 143 // to print additional information about it. 144 f.Close() 145 } 146 return stmts, err 147 } 148 149 // ParseData reads the given byteslice and parses it into a set of statements. 150 // The 'filename' argument is only used in case of errors so doesn't necessarily have to correspond to a real file. 151 func (p *Parser) ParseData(data []byte, filename string) ([]*Statement, error) { 152 r := &namedReader{r: bytes.NewReader(data), name: filename} 153 return p.parseAndHandleErrors(r, filename) 154 } 155 156 // parseAndHandleErrors handles errors nicely if the given input fails to parse. 157 func (p *Parser) parseAndHandleErrors(r io.ReadSeeker, filename string) ([]*Statement, error) { 158 input, err := parseFileInput(r) 159 if err == nil { 160 return input.Statements, nil 161 } 162 // If we get here, something went wrong. Try to give some nice feedback about it. 163 return nil, p.annotate(err, r) 164 } 165 166 // annotate annotates the given error with whatever source information we have. 167 func (p *Parser) annotate(err error, r io.ReadSeeker) error { 168 err = AddReader(err, r) 169 // Now annotate with any builtin rules we might have loaded. 170 for filename, contents := range p.builtins { 171 err = AddReader(err, &namedReader{r: bytes.NewReader(contents), name: filename}) 172 } 173 return err 174 } 175 176 // optimise implements some (very) mild optimisations on the given set of statements to translate them 177 // into a form we find slightly more useful. 178 // This also sneaks in some rewrites to .append and .extend which are very troublesome otherwise 179 // (technically that changes the meaning of the code, #dealwithit) 180 func (p *Parser) optimise(statements []*Statement) []*Statement { 181 ret := make([]*Statement, 0, len(statements)) 182 for _, stmt := range statements { 183 if stmt.Literal != nil || stmt.Pass { 184 continue // Neither statement has any effect. 185 } else if stmt.FuncDef != nil { 186 stmt.FuncDef.Statements = p.optimise(stmt.FuncDef.Statements) 187 } else if stmt.For != nil { 188 stmt.For.Statements = p.optimise(stmt.For.Statements) 189 } else if stmt.If != nil { 190 stmt.If.Statements = p.optimise(stmt.If.Statements) 191 for i, elif := range stmt.If.Elif { 192 stmt.If.Elif[i].Statements = p.optimise(elif.Statements) 193 } 194 stmt.If.ElseStatements = p.optimise(stmt.If.ElseStatements) 195 } else if stmt.Ident != nil && stmt.Ident.Action != nil && stmt.Ident.Action.Property != nil && len(stmt.Ident.Action.Property.Action) == 1 { 196 call := stmt.Ident.Action.Property.Action[0].Call 197 name := stmt.Ident.Action.Property.Name 198 if (name == "append" || name == "extend") && call != nil && len(call.Arguments) == 1 { 199 stmt = &Statement{ 200 Pos: stmt.Pos, 201 Ident: &IdentStatement{ 202 Name: stmt.Ident.Name, 203 Action: &IdentStatementAction{ 204 AugAssign: &call.Arguments[0].Value, 205 }, 206 }, 207 } 208 if name == "append" { 209 stmt.Ident.Action.AugAssign = &Expression{Val: &ValueExpression{ 210 List: &List{ 211 Values: []*Expression{&call.Arguments[0].Value}, 212 }, 213 }} 214 } 215 } 216 } 217 ret = append(ret, stmt) 218 } 219 return ret 220 } 221 222 // whitelistedKwargs returns true if the given built-in function name is allowed to 223 // be called as non-kwargs. 224 // TODO(peterebden): Come up with a syntax that exposes this directly in the file. 225 func whitelistedKwargs(name, filename string) bool { 226 if name[0] == '_' || (strings.HasSuffix(filename, "builtins.build_defs") && name != "build_rule") { 227 return true // Don't care about anything private, or non-rule builtins. 228 } 229 return map[string]bool{ 230 "workspace": true, 231 "decompose": true, 232 "check_config": true, 233 "select": true, 234 }[name] 235 }