/* * Copyright (C) 2019 The Android Open Source Project * * 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 com.android.codegen /** * Mixin for syntactic sugar around indent-aware printing into [stringBuilder] */ interface Printer> { val stringBuilder: StringBuilder var currentIndent: String fun pushIndent() { currentIndent += INDENT_SINGLE } fun popIndent() { currentIndent = if (currentIndent.length >= INDENT_SINGLE.length) { currentIndent.substring(0, currentIndent.length - INDENT_SINGLE.length) } else { "" } } fun backspace() = stringBuilder.setLength(stringBuilder.length - 1) val lastChar get() = stringBuilder[stringBuilder.length - 1] private fun appendRaw(s: String) { stringBuilder.append(s) } fun append(s: String) { if (s.isBlank() && s != "\n") { appendRaw(s) } else { appendRaw(s.lines().map { line -> if (line.startsWith(" *")) line else line.trimStart() }.joinToString("\n$currentIndent")) } } fun appendSameLine(s: String) { while (lastChar.isWhitespace() || lastChar.isNewline()) { backspace() } appendRaw(s) } fun rmEmptyLine() { while (lastChar.isWhitespaceNonNewline()) backspace() if (lastChar.isNewline()) backspace() } /** * Syntactic sugar for: * ``` * +"code()"; * ``` * to append the given string plus a newline */ operator fun String.unaryPlus() = append("$this\n") /** * Syntactic sugar for: * ``` * !"code()"; * ``` * to append the given string without a newline */ operator fun String.not() = append(this) /** * Syntactic sugar for: * ``` * ... { * ... * }+";" * ``` * to append a ';' on same line after a block, and a newline afterwards */ operator fun Unit.plus(s: String) { appendSameLine(s) +"" } /** * A multi-purpose syntactic sugar for appending the given string plus anything generated in * the given [block], the latter with the appropriate deeper indent, * and resetting the indent back to original at the end * * Usage examples: * * ``` * "if (...)" { * ... * } * ``` * to append a corresponding if block appropriate indentation * * ``` * "void foo(...)" { * ... * } * ``` * similar to the previous one, plus an extra empty line after the function body * * ``` * "void foo(" { * * } * ``` * to use proper indentation for args code and close the bracket on same line at end * * ``` * "..." { * ... * } * to use the correct indentation for inner code, resetting it at the end */ operator fun String.invoke(block: SELF.() -> Unit) { if (this == " {") { appendSameLine(this) } else { append(this) } when { endsWith("(") -> { indentedBy(2, block) appendSameLine(")") } endsWith("{") || endsWith(")") -> { if (!endsWith("{")) appendSameLine(" {") indentedBy(1, block) +"}" if ((endsWith(") {") || endsWith(")") || this == " {") && !startsWith("synchronized") && !startsWith("switch") && !startsWith("if ") && !contains(" else ") && !contains("new ") && !contains("return ")) { +"" // extra line after function definitions } } else -> indentedBy(2, block) } } fun indentedBy(level: Int, block: SELF.() -> Unit) { append("\n") level times { append(INDENT_SINGLE) pushIndent() } (this as SELF).block() level times { popIndent() } rmEmptyLine() +"" } fun Iterable.forEachTrimmingTrailingComma(b: FieldInfo.() -> Unit) { forEachApply { b() if (isLast) { while (lastChar == ' ' || lastChar == '\n') backspace() if (lastChar == ',') backspace() } } } }