| // 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" |
| ) |
| |
| // Represents an expression in the Starlark code. An expression has a type. |
| type starlarkExpr interface { |
| starlarkNode |
| typ() starlarkType |
| // Emit the code to copy the expression, otherwise we will end up |
| // with source and target pointing to the same list. |
| emitListVarCopy(gctx *generationContext) |
| // Return the expression, calling the transformer func for |
| // every expression in the tree. If the transformer func returns non-nil, |
| // its result is used in place of the expression it was called with in the |
| // resulting expression. The resulting starlarkExpr will contain as many |
| // of the same objects from the original expression as possible. |
| transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr |
| } |
| |
| func maybeString(expr starlarkExpr) (string, bool) { |
| if x, ok := expr.(*stringLiteralExpr); ok { |
| return x.literal, true |
| } |
| return "", false |
| } |
| |
| type stringLiteralExpr struct { |
| literal string |
| } |
| |
| func (s *stringLiteralExpr) emit(gctx *generationContext) { |
| gctx.writef("%q", s.literal) |
| } |
| |
| func (_ *stringLiteralExpr) typ() starlarkType { |
| return starlarkTypeString |
| } |
| |
| func (s *stringLiteralExpr) emitListVarCopy(gctx *generationContext) { |
| s.emit(gctx) |
| } |
| |
| func (s *stringLiteralExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr { |
| if replacement := transformer(s); replacement != nil { |
| return replacement |
| } else { |
| return s |
| } |
| } |
| |
| // Integer literal |
| type intLiteralExpr struct { |
| literal int |
| } |
| |
| func (s *intLiteralExpr) emit(gctx *generationContext) { |
| gctx.writef("%d", s.literal) |
| } |
| |
| func (_ *intLiteralExpr) typ() starlarkType { |
| return starlarkTypeInt |
| } |
| |
| func (s *intLiteralExpr) emitListVarCopy(gctx *generationContext) { |
| s.emit(gctx) |
| } |
| |
| func (s *intLiteralExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr { |
| if replacement := transformer(s); replacement != nil { |
| return replacement |
| } else { |
| return s |
| } |
| } |
| |
| // Boolean literal |
| type boolLiteralExpr struct { |
| literal bool |
| } |
| |
| 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) |
| } |
| |
| func (b *boolLiteralExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr { |
| if replacement := transformer(b); replacement != nil { |
| return replacement |
| } else { |
| return b |
| } |
| } |
| |
| type globalsExpr struct { |
| } |
| |
| func (g *globalsExpr) emit(gctx *generationContext) { |
| gctx.write("g") |
| } |
| |
| func (g *globalsExpr) typ() starlarkType { |
| return starlarkTypeUnknown |
| } |
| |
| func (g *globalsExpr) emitListVarCopy(gctx *generationContext) { |
| g.emit(gctx) |
| } |
| |
| func (g *globalsExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr { |
| if replacement := transformer(g); replacement != nil { |
| return replacement |
| } else { |
| return g |
| } |
| } |
| |
| // interpolateExpr represents Starlark's interpolation operator <string> % list |
| // we break <string> into a list of chunks, i.e., "first%second%third" % (X, Y) |
| // will have chunks = ["first", "second", "third"] and args = [X, Y] |
| type interpolateExpr struct { |
| chunks []string // string chunks, separated by '%' |
| args []starlarkExpr |
| } |
| |
| func NewInterpolateExpr(parts []starlarkExpr) starlarkExpr { |
| result := &interpolateExpr{} |
| needString := true |
| for _, part := range parts { |
| if needString { |
| if strLit, ok := part.(*stringLiteralExpr); ok { |
| result.chunks = append(result.chunks, strLit.literal) |
| } else { |
| result.chunks = append(result.chunks, "") |
| } |
| needString = false |
| } else { |
| if strLit, ok := part.(*stringLiteralExpr); ok { |
| result.chunks[len(result.chunks)-1] += strLit.literal |
| } else { |
| result.args = append(result.args, part) |
| needString = true |
| } |
| } |
| } |
| if len(result.chunks) == len(result.args) { |
| result.chunks = append(result.chunks, "") |
| } |
| if len(result.args) == 0 { |
| return &stringLiteralExpr{literal: strings.Join(result.chunks, "")} |
| } |
| return result |
| } |
| |
| func (xi *interpolateExpr) emit(gctx *generationContext) { |
| if len(xi.chunks) != len(xi.args)+1 { |
| panic(fmt.Errorf("malformed interpolateExpr: #chunks(%d) != #args(%d)+1", |
| len(xi.chunks), len(xi.args))) |
| } |
| // Generate format as join of chunks, but first escape '%' in them |
| format := strings.ReplaceAll(xi.chunks[0], "%", "%%") |
| for _, chunk := range xi.chunks[1:] { |
| format += "%s" + strings.ReplaceAll(chunk, "%", "%%") |
| } |
| gctx.writef("%q %% ", format) |
| emitArg := func(arg starlarkExpr) { |
| if arg.typ() == starlarkTypeList { |
| gctx.write(`" ".join(`) |
| arg.emit(gctx) |
| gctx.write(`)`) |
| } else { |
| arg.emit(gctx) |
| } |
| } |
| if len(xi.args) == 1 { |
| emitArg(xi.args[0]) |
| } else { |
| sep := "(" |
| for _, arg := range xi.args { |
| gctx.write(sep) |
| emitArg(arg) |
| sep = ", " |
| } |
| gctx.write(")") |
| } |
| } |
| |
| func (_ *interpolateExpr) typ() starlarkType { |
| return starlarkTypeString |
| } |
| |
| func (xi *interpolateExpr) emitListVarCopy(gctx *generationContext) { |
| xi.emit(gctx) |
| } |
| |
| func (xi *interpolateExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr { |
| for i := range xi.args { |
| xi.args[i] = xi.args[i].transform(transformer) |
| } |
| if replacement := transformer(xi); replacement != nil { |
| return replacement |
| } else { |
| return xi |
| } |
| } |
| |
| type variableRefExpr struct { |
| ref variable |
| } |
| |
| func NewVariableRefExpr(ref variable) starlarkExpr { |
| if predefined, ok := ref.(*predefinedVariable); ok { |
| return predefined.value |
| } |
| return &variableRefExpr{ref} |
| } |
| |
| func (v *variableRefExpr) emit(gctx *generationContext) { |
| v.ref.emitGet(gctx) |
| } |
| |
| func (v *variableRefExpr) typ() starlarkType { |
| return v.ref.valueType() |
| } |
| |
| func (v *variableRefExpr) emitListVarCopy(gctx *generationContext) { |
| v.emit(gctx) |
| if v.typ() == starlarkTypeList { |
| gctx.write("[:]") // this will copy the list |
| } |
| } |
| |
| func (v *variableRefExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr { |
| if replacement := transformer(v); replacement != nil { |
| return replacement |
| } else { |
| return v |
| } |
| } |
| |
| type toStringExpr struct { |
| expr starlarkExpr |
| } |
| |
| func (s *toStringExpr) emit(ctx *generationContext) { |
| switch s.expr.typ() { |
| case starlarkTypeString, starlarkTypeUnknown: |
| // Assume unknown types are strings already. |
| s.expr.emit(ctx) |
| case starlarkTypeList: |
| ctx.write(`" ".join(`) |
| s.expr.emit(ctx) |
| ctx.write(")") |
| case starlarkTypeInt: |
| ctx.write(`("%d" % (`) |
| s.expr.emit(ctx) |
| ctx.write("))") |
| case starlarkTypeBool: |
| ctx.write(`("true" if (`) |
| s.expr.emit(ctx) |
| ctx.write(`) else "")`) |
| case starlarkTypeVoid: |
| ctx.write(`""`) |
| default: |
| panic("Unknown starlark type!") |
| } |
| } |
| |
| func (s *toStringExpr) typ() starlarkType { |
| return starlarkTypeString |
| } |
| |
| func (s *toStringExpr) emitListVarCopy(gctx *generationContext) { |
| s.emit(gctx) |
| } |
| |
| func (s *toStringExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr { |
| s.expr = s.expr.transform(transformer) |
| if replacement := transformer(s); replacement != nil { |
| return replacement |
| } else { |
| return s |
| } |
| } |
| |
| type notExpr struct { |
| expr starlarkExpr |
| } |
| |
| func (n *notExpr) emit(ctx *generationContext) { |
| ctx.write("not ") |
| n.expr.emit(ctx) |
| } |
| |
| func (_ *notExpr) typ() starlarkType { |
| return starlarkTypeBool |
| } |
| |
| func (n *notExpr) emitListVarCopy(gctx *generationContext) { |
| n.emit(gctx) |
| } |
| |
| func (n *notExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr { |
| n.expr = n.expr.transform(transformer) |
| if replacement := transformer(n); replacement != nil { |
| return replacement |
| } else { |
| return n |
| } |
| } |
| |
| type eqExpr struct { |
| left, right starlarkExpr |
| isEq bool // if false, it's != |
| } |
| |
| func (eq *eqExpr) emit(gctx *generationContext) { |
| if eq.left.typ() != eq.right.typ() { |
| eq.left = &toStringExpr{expr: eq.left} |
| eq.right = &toStringExpr{expr: eq.right} |
| } |
| |
| // General case |
| eq.left.emit(gctx) |
| if eq.isEq { |
| gctx.write(" == ") |
| } else { |
| gctx.write(" != ") |
| } |
| eq.right.emit(gctx) |
| } |
| |
| func (_ *eqExpr) typ() starlarkType { |
| return starlarkTypeBool |
| } |
| |
| func (eq *eqExpr) emitListVarCopy(gctx *generationContext) { |
| eq.emit(gctx) |
| } |
| |
| func (eq *eqExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr { |
| eq.left = eq.left.transform(transformer) |
| eq.right = eq.right.transform(transformer) |
| if replacement := transformer(eq); replacement != nil { |
| return replacement |
| } else { |
| return eq |
| } |
| } |
| |
| type listExpr struct { |
| items []starlarkExpr |
| } |
| |
| func (l *listExpr) emit(gctx *generationContext) { |
| if !gctx.inAssignment || len(l.items) < 2 { |
| gctx.write("[") |
| sep := "" |
| for _, item := range l.items { |
| gctx.write(sep) |
| item.emit(gctx) |
| sep = ", " |
| } |
| gctx.write("]") |
| return |
| } |
| |
| gctx.write("[") |
| gctx.indentLevel += 2 |
| |
| for _, item := range l.items { |
| gctx.newLine() |
| item.emit(gctx) |
| gctx.write(",") |
| } |
| gctx.indentLevel -= 2 |
| gctx.newLine() |
| gctx.write("]") |
| } |
| |
| func (_ *listExpr) typ() starlarkType { |
| return starlarkTypeList |
| } |
| |
| func (l *listExpr) emitListVarCopy(gctx *generationContext) { |
| l.emit(gctx) |
| } |
| |
| func (l *listExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr { |
| itemsCopy := make([]starlarkExpr, len(l.items)) |
| for i, item := range l.items { |
| itemsCopy[i] = item.transform(transformer) |
| } |
| l.items = itemsCopy |
| if replacement := transformer(l); replacement != nil { |
| return replacement |
| } else { |
| return l |
| } |
| } |
| |
| func newStringListExpr(items []string) *listExpr { |
| v := listExpr{} |
| for _, item := range items { |
| v.items = append(v.items, &stringLiteralExpr{item}) |
| } |
| return &v |
| } |
| |
| // concatExpr generates expr1 + expr2 + ... + exprN in Starlark. |
| type concatExpr struct { |
| items []starlarkExpr |
| } |
| |
| func (c *concatExpr) emit(gctx *generationContext) { |
| if len(c.items) == 1 { |
| c.items[0].emit(gctx) |
| return |
| } |
| |
| if !gctx.inAssignment { |
| c.items[0].emit(gctx) |
| for _, item := range c.items[1:] { |
| gctx.write(" + ") |
| item.emit(gctx) |
| } |
| return |
| } |
| gctx.write("(") |
| c.items[0].emit(gctx) |
| gctx.indentLevel += 2 |
| for _, item := range c.items[1:] { |
| gctx.write(" +") |
| gctx.newLine() |
| item.emit(gctx) |
| } |
| gctx.write(")") |
| gctx.indentLevel -= 2 |
| } |
| |
| func (_ *concatExpr) typ() starlarkType { |
| return starlarkTypeList |
| } |
| |
| func (c *concatExpr) emitListVarCopy(gctx *generationContext) { |
| c.emit(gctx) |
| } |
| |
| func (c *concatExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr { |
| itemsCopy := make([]starlarkExpr, len(c.items)) |
| for i, item := range c.items { |
| itemsCopy[i] = item.transform(transformer) |
| } |
| c.items = itemsCopy |
| if replacement := transformer(c); replacement != nil { |
| return replacement |
| } else { |
| return c |
| } |
| } |
| |
| // inExpr generates <expr> [not] in <list> |
| type inExpr struct { |
| expr starlarkExpr |
| list starlarkExpr |
| isNot bool |
| } |
| |
| func (i *inExpr) emit(gctx *generationContext) { |
| i.expr.emit(gctx) |
| if i.isNot { |
| gctx.write(" not in ") |
| } else { |
| gctx.write(" in ") |
| } |
| i.list.emit(gctx) |
| } |
| |
| func (_ *inExpr) typ() starlarkType { |
| return starlarkTypeBool |
| } |
| |
| func (i *inExpr) emitListVarCopy(gctx *generationContext) { |
| i.emit(gctx) |
| } |
| |
| func (i *inExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr { |
| i.expr = i.expr.transform(transformer) |
| i.list = i.list.transform(transformer) |
| if replacement := transformer(i); replacement != nil { |
| return replacement |
| } else { |
| return i |
| } |
| } |
| |
| type indexExpr struct { |
| array starlarkExpr |
| index starlarkExpr |
| } |
| |
| func (ix *indexExpr) emit(gctx *generationContext) { |
| ix.array.emit(gctx) |
| gctx.write("[") |
| ix.index.emit(gctx) |
| gctx.write("]") |
| } |
| |
| func (ix *indexExpr) typ() starlarkType { |
| return starlarkTypeString |
| } |
| |
| func (ix *indexExpr) emitListVarCopy(gctx *generationContext) { |
| ix.emit(gctx) |
| } |
| |
| func (ix *indexExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr { |
| ix.array = ix.array.transform(transformer) |
| ix.index = ix.index.transform(transformer) |
| if replacement := transformer(ix); replacement != nil { |
| return replacement |
| } else { |
| return ix |
| } |
| } |
| |
| type callExpr struct { |
| object starlarkExpr // nil if static call |
| name string |
| args []starlarkExpr |
| returnType starlarkType |
| } |
| |
| func (cx *callExpr) emit(gctx *generationContext) { |
| if cx.object != nil { |
| gctx.write("(") |
| cx.object.emit(gctx) |
| gctx.write(")") |
| gctx.write(".", cx.name, "(") |
| } else { |
| gctx.write(cx.name, "(") |
| } |
| sep := "" |
| for _, arg := range cx.args { |
| gctx.write(sep) |
| arg.emit(gctx) |
| sep = ", " |
| } |
| gctx.write(")") |
| } |
| |
| func (cx *callExpr) typ() starlarkType { |
| return cx.returnType |
| } |
| |
| func (cx *callExpr) emitListVarCopy(gctx *generationContext) { |
| cx.emit(gctx) |
| } |
| |
| func (cx *callExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr { |
| if cx.object != nil { |
| cx.object = cx.object.transform(transformer) |
| } |
| for i := range cx.args { |
| cx.args[i] = cx.args[i].transform(transformer) |
| } |
| if replacement := transformer(cx); replacement != nil { |
| return replacement |
| } else { |
| return cx |
| } |
| } |
| |
| type ifExpr struct { |
| condition starlarkExpr |
| ifTrue starlarkExpr |
| ifFalse starlarkExpr |
| } |
| |
| 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) |
| } |
| |
| func (i *ifExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr { |
| i.condition = i.condition.transform(transformer) |
| i.ifTrue = i.ifTrue.transform(transformer) |
| i.ifFalse = i.ifFalse.transform(transformer) |
| if replacement := transformer(i); replacement != nil { |
| return replacement |
| } else { |
| return i |
| } |
| } |
| |
| type identifierExpr struct { |
| name string |
| } |
| |
| func (i *identifierExpr) emit(gctx *generationContext) { |
| gctx.write(i.name) |
| } |
| |
| func (i *identifierExpr) typ() starlarkType { |
| return starlarkTypeUnknown |
| } |
| |
| func (i *identifierExpr) emitListVarCopy(gctx *generationContext) { |
| i.emit(gctx) |
| } |
| |
| func (i *identifierExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr { |
| if replacement := transformer(i); replacement != nil { |
| return replacement |
| } else { |
| return i |
| } |
| } |
| |
| type foreachExpr struct { |
| varName string |
| list starlarkExpr |
| action starlarkExpr |
| } |
| |
| func (f *foreachExpr) emit(gctx *generationContext) { |
| gctx.write("[") |
| f.action.emit(gctx) |
| gctx.write(" for " + f.varName + " in ") |
| f.list.emit(gctx) |
| gctx.write("]") |
| } |
| |
| func (f *foreachExpr) typ() starlarkType { |
| return starlarkTypeList |
| } |
| |
| func (f *foreachExpr) emitListVarCopy(gctx *generationContext) { |
| f.emit(gctx) |
| } |
| |
| func (f *foreachExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr { |
| f.list = f.list.transform(transformer) |
| f.action = f.action.transform(transformer) |
| if replacement := transformer(f); replacement != nil { |
| return replacement |
| } else { |
| return f |
| } |
| } |
| |
| type binaryOpExpr struct { |
| left, right starlarkExpr |
| op string |
| returnType starlarkType |
| } |
| |
| func (b *binaryOpExpr) emit(gctx *generationContext) { |
| b.left.emit(gctx) |
| gctx.write(" " + b.op + " ") |
| b.right.emit(gctx) |
| } |
| |
| func (b *binaryOpExpr) typ() starlarkType { |
| return b.returnType |
| } |
| |
| func (b *binaryOpExpr) emitListVarCopy(gctx *generationContext) { |
| b.emit(gctx) |
| } |
| |
| func (b *binaryOpExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr { |
| b.left = b.left.transform(transformer) |
| b.right = b.right.transform(transformer) |
| if replacement := transformer(b); replacement != nil { |
| return replacement |
| } else { |
| return b |
| } |
| } |
| |
| type badExpr struct { |
| errorLocation ErrorLocation |
| message string |
| } |
| |
| func (b *badExpr) emit(gctx *generationContext) { |
| gctx.emitConversionError(b.errorLocation, b.message) |
| } |
| |
| func (_ *badExpr) typ() starlarkType { |
| return starlarkTypeUnknown |
| } |
| |
| func (_ *badExpr) emitListVarCopy(_ *generationContext) { |
| panic("implement me") |
| } |
| |
| func (b *badExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr { |
| if replacement := transformer(b); replacement != nil { |
| return replacement |
| } else { |
| return b |
| } |
| } |
| |
| func maybeConvertToStringList(expr starlarkExpr) starlarkExpr { |
| if xString, ok := expr.(*stringLiteralExpr); ok { |
| return newStringListExpr(strings.Fields(xString.literal)) |
| } |
| return expr |
| } |
| |
| func isEmptyString(expr starlarkExpr) bool { |
| x, ok := expr.(*stringLiteralExpr) |
| return ok && x.literal == "" |
| } |
| |
| func negateExpr(expr starlarkExpr) starlarkExpr { |
| switch typedExpr := expr.(type) { |
| case *notExpr: |
| return typedExpr.expr |
| case *inExpr: |
| typedExpr.isNot = !typedExpr.isNot |
| return typedExpr |
| case *eqExpr: |
| typedExpr.isEq = !typedExpr.isEq |
| return typedExpr |
| case *binaryOpExpr: |
| switch typedExpr.op { |
| case ">": |
| typedExpr.op = "<=" |
| return typedExpr |
| case "<": |
| typedExpr.op = ">=" |
| return typedExpr |
| case ">=": |
| typedExpr.op = "<" |
| return typedExpr |
| case "<=": |
| typedExpr.op = ">" |
| return typedExpr |
| default: |
| return ¬Expr{expr: expr} |
| } |
| default: |
| return ¬Expr{expr: expr} |
| } |
| } |