From 4eadba7438f87d35e5b1b830192a537f15d010fc Mon Sep 17 00:00:00 2001 From: Cole Faust Date: Tue, 7 Dec 2021 11:54:52 -0800 Subject: [PATCH] Support if expressions in mk2rbc Bug: 201700692 Test: go test Change-Id: Icdf30c4d625d81974db946bd91660a29a0373ac7 --- mk2rbc/expr.go | 74 +++++++++++++++++++++++++++++++++++++++++++ mk2rbc/mk2rbc.go | 32 +++++++++++++++++++ mk2rbc/mk2rbc_test.go | 23 ++++++++++++++ 3 files changed, 129 insertions(+) diff --git a/mk2rbc/expr.go b/mk2rbc/expr.go index ec0b279b9..81b31c736 100644 --- a/mk2rbc/expr.go +++ b/mk2rbc/expr.go @@ -85,6 +85,31 @@ func (s *intLiteralExpr) emitListVarCopy(gctx *generationContext) { s.emit(gctx) } +// Boolean literal +type boolLiteralExpr struct { + literal bool +} + +func (b *boolLiteralExpr) eval(_ map[string]starlarkExpr) (res starlarkExpr, same bool) { + return b, true +} + +func (b *boolLiteralExpr) emit(gctx *generationContext) { + if b.literal { + gctx.write("True") + } else { + gctx.write("False") + } +} + +func (_ *boolLiteralExpr) typ() starlarkType { + return starlarkTypeBool +} + +func (b *boolLiteralExpr) emitListVarCopy(gctx *generationContext) { + b.emit(gctx) +} + // interpolateExpr represents Starlark's interpolation operator % list // we break into a list of chunks, i.e., "first%second%third" % (X, Y) // will have chunks = ["first", "second", "third"] and args = [X, Y] @@ -617,6 +642,55 @@ func (cx *callExpr) emitListVarCopy(gctx *generationContext) { cx.emit(gctx) } +type ifExpr struct { + condition starlarkExpr + ifTrue starlarkExpr + ifFalse starlarkExpr +} + +func (i *ifExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) { + cond, condSame := i.condition.eval(valueMap) + t, tSame := i.ifTrue.eval(valueMap) + f, fSame := i.ifFalse.eval(valueMap) + same = condSame && tSame && fSame + if same { + return i, same + } else { + return &ifExpr{ + condition: cond, + ifTrue: t, + ifFalse: f, + }, same + } +} + +func (i *ifExpr) emit(gctx *generationContext) { + gctx.write("(") + i.ifTrue.emit(gctx) + gctx.write(" if ") + i.condition.emit(gctx) + gctx.write(" else ") + i.ifFalse.emit(gctx) + gctx.write(")") +} + +func (i *ifExpr) typ() starlarkType { + tType := i.ifTrue.typ() + fType := i.ifFalse.typ() + if tType != fType && tType != starlarkTypeUnknown && fType != starlarkTypeUnknown { + panic("Conflicting types in if expression") + } + if tType != starlarkTypeUnknown { + return tType + } else { + return fType + } +} + +func (i *ifExpr) emitListVarCopy(gctx *generationContext) { + i.emit(gctx) +} + type badExpr struct { errorLocation ErrorLocation message string diff --git a/mk2rbc/mk2rbc.go b/mk2rbc/mk2rbc.go index cade4d20f..d1140fab7 100644 --- a/mk2rbc/mk2rbc.go +++ b/mk2rbc/mk2rbc.go @@ -112,6 +112,7 @@ var knownFunctions = map[string]struct { "filter-out": {baseName + ".filter_out", starlarkTypeList, hiddenArgNone}, "firstword": {"!firstword", starlarkTypeString, hiddenArgNone}, "get-vendor-board-platforms": {"!get-vendor-board-platforms", starlarkTypeList, hiddenArgNone}, // internal macro, used by is-board-platform, etc. + "if": {"!if", starlarkTypeUnknown, hiddenArgNone}, "info": {baseName + ".mkinfo", starlarkTypeVoid, hiddenArgNone}, "is-android-codename": {"!is-android-codename", starlarkTypeBool, hiddenArgNone}, // unused by product config "is-android-codename-in-list": {"!is-android-codename-in-list", starlarkTypeBool, hiddenArgNone}, // unused by product config @@ -1368,6 +1369,8 @@ func (ctx *parseContext) parseReference(node mkparser.Node, ref *mkparser.MakeSt return ctx.newBadExpr(node, "cannot handle invoking %s", expr.name) } switch expr.name { + case "if": + return ctx.parseIfFunc(node, args) case "word": return ctx.parseWordFunc(node, args) case "firstword", "lastword": @@ -1423,6 +1426,35 @@ func (ctx *parseContext) parseSubstFunc(node mkparser.Node, fname string, args * } } +func (ctx *parseContext) parseIfFunc(node mkparser.Node, args *mkparser.MakeString) starlarkExpr { + words := args.Split(",") + if len(words) != 2 && len(words) != 3 { + return ctx.newBadExpr(node, "if function should have 2 or 3 arguments, found "+strconv.Itoa(len(words))) + } + condition := ctx.parseMakeString(node, words[0]) + ifTrue := ctx.parseMakeString(node, words[1]) + var ifFalse starlarkExpr + if len(words) == 3 { + ifFalse = ctx.parseMakeString(node, words[2]) + } else { + switch ifTrue.typ() { + case starlarkTypeList: + ifFalse = &listExpr{items: []starlarkExpr{}} + case starlarkTypeInt: + ifFalse = &intLiteralExpr{literal: 0} + case starlarkTypeBool: + ifFalse = &boolLiteralExpr{literal: false} + default: + ifFalse = &stringLiteralExpr{literal: ""} + } + } + return &ifExpr{ + condition, + ifTrue, + ifFalse, + } +} + func (ctx *parseContext) parseWordFunc(node mkparser.Node, args *mkparser.MakeString) starlarkExpr { words := args.Split(",") if len(words) != 2 { diff --git a/mk2rbc/mk2rbc_test.go b/mk2rbc/mk2rbc_test.go index fa33e75d5..0048ad122 100644 --- a/mk2rbc/mk2rbc_test.go +++ b/mk2rbc/mk2rbc_test.go @@ -1089,6 +1089,29 @@ def init(g, handle): cfg = rblf.cfg(handle) if rblf.mk2rbc_error("build/product.mk:2", "cannot handle invoking foobar"): pass +`, + }, + { + desc: "if expression", + mkname: "product.mk", + in: ` +TEST_VAR := foo +TEST_VAR_LIST := foo +TEST_VAR_LIST += bar +TEST_VAR_2 := $(if $(TEST_VAR),bar) +TEST_VAR_3 := $(if $(TEST_VAR),bar,baz) +TEST_VAR_3 := $(if $(TEST_VAR),$(TEST_VAR_LIST)) +`, + expected: `load("//build/make/core:product_config.rbc", "rblf") + +def init(g, handle): + cfg = rblf.cfg(handle) + g["TEST_VAR"] = "foo" + g["TEST_VAR_LIST"] = ["foo"] + g["TEST_VAR_LIST"] += ["bar"] + g["TEST_VAR_2"] = ("bar" if g["TEST_VAR"] else "") + g["TEST_VAR_3"] = ("bar" if g["TEST_VAR"] else "baz") + g["TEST_VAR_3"] = (g["TEST_VAR_LIST"] if g["TEST_VAR"] else []) `, }, }