Files
build_soong/mk2rbc/node.go
Cole Faust dd569aea07 Return starlarkNodes from the functions that parse them
Currently, mk2rbc is structured around having a global
"receiver" object that accepts all starlarkNodes. As soon
as they are parsed they are added to the receiver.

Returning the parsed nodes to the calling function is more
flexible, as it allows the calling function to restructure
them as necessary. This is the first step to supporting
complicated statements involving $(eval), such as
`$(foreach v,$(MY_LIST),$(eval MY_LIST_2 += $(v)))`

Test: go test
Change-Id: Ia194123cf090d2b9559a25b975ccbc5749357cc0
2022-02-02 14:59:59 -08:00

284 lines
6.5 KiB
Go

// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mk2rbc
import (
"fmt"
"strings"
mkparser "android/soong/androidmk/parser"
)
// A parsed node for which starlark code will be generated
// by calling emit().
type starlarkNode interface {
emit(ctx *generationContext)
}
// Types used to keep processed makefile data:
type commentNode struct {
text string
}
func (c *commentNode) emit(gctx *generationContext) {
chunks := strings.Split(c.text, "\\\n")
gctx.newLine()
gctx.write(chunks[0]) // It has '#' at the beginning already.
for _, chunk := range chunks[1:] {
gctx.newLine()
gctx.write("#", chunk)
}
}
type moduleInfo struct {
path string // Converted Starlark file path
originalPath string // Makefile file path
moduleLocalName string
optional bool
missing bool // a module may not exist if a module that depends on it is loaded dynamically
}
func (im moduleInfo) entryName() string {
return im.moduleLocalName + "_init"
}
func (mi moduleInfo) name() string {
return fmt.Sprintf("%q", MakePath2ModuleName(mi.originalPath))
}
type inheritedModule interface {
name() string
entryName() string
emitSelect(gctx *generationContext)
pathExpr() starlarkExpr
needsLoadCheck() bool
}
type inheritedStaticModule struct {
*moduleInfo
loadAlways bool
}
func (im inheritedStaticModule) emitSelect(_ *generationContext) {
}
func (im inheritedStaticModule) pathExpr() starlarkExpr {
return &stringLiteralExpr{im.path}
}
func (im inheritedStaticModule) needsLoadCheck() bool {
return im.missing
}
type inheritedDynamicModule struct {
path interpolateExpr
candidateModules []*moduleInfo
loadAlways bool
location ErrorLocation
needsWarning bool
}
func (i inheritedDynamicModule) name() string {
return "_varmod"
}
func (i inheritedDynamicModule) entryName() string {
return i.name() + "_init"
}
func (i inheritedDynamicModule) emitSelect(gctx *generationContext) {
if i.needsWarning {
gctx.newLine()
gctx.writef("%s.mkwarning(%q, %q)", baseName, i.location, "Including a path with a non-constant prefix, please convert this to a simple literal to generate cleaner starlark.")
}
gctx.newLine()
gctx.writef("_entry = {")
gctx.indentLevel++
for _, mi := range i.candidateModules {
gctx.newLine()
gctx.writef(`"%s": (%s, %s),`, mi.originalPath, mi.name(), mi.entryName())
}
gctx.indentLevel--
gctx.newLine()
gctx.write("}.get(")
i.path.emit(gctx)
gctx.write(")")
gctx.newLine()
gctx.writef("(%s, %s) = _entry if _entry else (None, None)", i.name(), i.entryName())
}
func (i inheritedDynamicModule) pathExpr() starlarkExpr {
return &i.path
}
func (i inheritedDynamicModule) needsLoadCheck() bool {
return true
}
type inheritNode struct {
module inheritedModule
loadAlways bool
}
func (inn *inheritNode) emit(gctx *generationContext) {
// Unconditional case:
// maybe check that loaded
// rblf.inherit(handle, <module>, module_init)
// Conditional case:
// if <module>_init != None:
// same as above
inn.module.emitSelect(gctx)
name := inn.module.name()
entry := inn.module.entryName()
if inn.loadAlways {
gctx.emitLoadCheck(inn.module)
gctx.newLine()
gctx.writef("%s(handle, %s, %s)", cfnInherit, name, entry)
return
}
gctx.newLine()
gctx.writef("if %s:", entry)
gctx.indentLevel++
gctx.newLine()
gctx.writef("%s(handle, %s, %s)", cfnInherit, name, entry)
gctx.indentLevel--
}
type includeNode struct {
module inheritedModule
loadAlways bool
}
func (inn *includeNode) emit(gctx *generationContext) {
inn.module.emitSelect(gctx)
entry := inn.module.entryName()
if inn.loadAlways {
gctx.emitLoadCheck(inn.module)
gctx.newLine()
gctx.writef("%s(g, handle)", entry)
return
}
gctx.newLine()
gctx.writef("if %s != None:", entry)
gctx.indentLevel++
gctx.newLine()
gctx.writef("%s(g, handle)", entry)
gctx.indentLevel--
}
type assignmentFlavor int
const (
// Assignment flavors
asgnSet assignmentFlavor = iota // := or =
asgnMaybeSet assignmentFlavor = iota // ?= and variable may be unset
asgnAppend assignmentFlavor = iota // += and variable has been set before
asgnMaybeAppend assignmentFlavor = iota // += and variable may be unset
)
type assignmentNode struct {
lhs variable
value starlarkExpr
mkValue *mkparser.MakeString
flavor assignmentFlavor
location ErrorLocation
isTraced bool
previous *assignmentNode
}
func (asgn *assignmentNode) emit(gctx *generationContext) {
gctx.newLine()
gctx.inAssignment = true
asgn.lhs.emitSet(gctx, asgn)
gctx.inAssignment = false
if asgn.isTraced {
gctx.newLine()
gctx.tracedCount++
gctx.writef(`print("%s.%d: %s := ", `, gctx.starScript.mkFile, gctx.tracedCount, asgn.lhs.name())
asgn.lhs.emitGet(gctx, true)
gctx.writef(")")
}
}
type exprNode struct {
expr starlarkExpr
}
func (exn *exprNode) emit(gctx *generationContext) {
gctx.newLine()
exn.expr.emit(gctx)
}
type ifNode struct {
isElif bool // true if this is 'elif' statement
expr starlarkExpr
}
func (in *ifNode) emit(gctx *generationContext) {
ifElif := "if "
if in.isElif {
ifElif = "elif "
}
gctx.newLine()
gctx.write(ifElif)
in.expr.emit(gctx)
gctx.write(":")
}
type elseNode struct{}
func (br *elseNode) emit(gctx *generationContext) {
gctx.newLine()
gctx.write("else:")
}
// switchCase represents as single if/elseif/else branch. All the necessary
// info about flavor (if/elseif/else) is supposed to be kept in `gate`.
type switchCase struct {
gate starlarkNode
nodes []starlarkNode
}
func (cb *switchCase) emit(gctx *generationContext) {
cb.gate.emit(gctx)
gctx.indentLevel++
hasStatements := false
for _, node := range cb.nodes {
if _, ok := node.(*commentNode); !ok {
hasStatements = true
}
node.emit(gctx)
}
if !hasStatements {
gctx.emitPass()
}
gctx.indentLevel--
}
// A single complete if ... elseif ... else ... endif sequences
type switchNode struct {
ssCases []*switchCase
}
func (ssw *switchNode) emit(gctx *generationContext) {
for _, ssCase := range ssw.ssCases {
ssCase.emit(gctx)
}
}