/*
* Copyright 2011 Google Inc. All Rights Reserved.
*
* 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.google.devtools.j2objc.gen;
import com.google.common.base.CharMatcher;
import com.google.common.io.LineReader;
import com.google.devtools.j2objc.ast.TreeNode;
import com.google.devtools.j2objc.util.UnicodeUtils;
import java.io.IOException;
import java.io.StringReader;
/**
* Builds source text. This is similar to a StringBuilder, but tracks line
* numbers and outputs them as CPP line directives when directed.
*
* @author Tom Ball
*/
public class SourceBuilder {
private final StringBuilder buffer = new StringBuilder();
private String currentFile;
private int indention = 0;
private int currentLine = -1;
/**
* If true, generate CPP line directives. It's necessary to store this
* here rather than directly use Options.getLineDirectives(), so that the
* header generator doesn't generate them.
*/
private final boolean emitLineDirectives;
public static final int DEFAULT_INDENTION = 2;
public static final int BEGINNING_OF_FILE = -1;
/**
* Create a new SourceBuilder.
*
* @param emitLineDirectives if true, generate CPP line directives
*/
public SourceBuilder(boolean emitLineDirectives) {
this(emitLineDirectives, BEGINNING_OF_FILE);
}
/**
* Create a new SourceBuilder, specifying the initial line number to begin
* syncing. This is normally only used for building complex statements,
* where the generated source is used in another builder.
*
* @param emitLineDirectives if true, generate CPP line directives
* @param startLine the initial line number, or -1 if at start of file
*/
public SourceBuilder(boolean emitLineDirectives, int startLine) {
this.emitLineDirectives = emitLineDirectives;
this.currentLine = startLine;
}
@Override
public String toString() {
return buffer.toString();
}
private static final CharMatcher NEWLINE_MATCHER = CharMatcher.is('\n');
public void print(String s) {
buffer.append(s);
currentLine += NEWLINE_MATCHER.countIn(s);
}
public void print(char c) {
buffer.append(c);
if (c == '\n') {
currentLine++;
}
}
public void print(char[] cs) {
for (char c : cs) {
print(c);
}
}
public void print(int i) {
buffer.append(i);
}
public void printf(String format, Object... args) {
print(UnicodeUtils.format(format, args));
}
public void println(String s) {
print(s);
newline();
}
public void println(char c) {
print(c);
newline();
}
public void newline() {
buffer.append('\n');
currentLine++;
}
public void indent() {
indention++;
}
public void unindent() {
indention--;
if (indention < 0) {
throw new AssertionError("unbalanced indents");
}
}
public void printIndent() {
buffer.append(pad(indention * DEFAULT_INDENTION));
}
// StringBuilder compatibility.
public SourceBuilder append(char c) {
print(c);
return this;
}
public SourceBuilder append(char[] c) {
print(c);
return this;
}
public SourceBuilder append(int i) {
print(i);
return this;
}
public SourceBuilder append(String s) {
print(s);
return this;
}
public char charAt(int i) {
return buffer.charAt(i);
}
public int length() {
return buffer.length();
}
public String substring(int start, int end) {
return buffer.substring(start, end);
}
public void replace(int start, int end, String str) {
buffer.replace(start, end, str);
}
public char[] pad(int n) {
if (n < 0) {
n = 0;
}
char[] result = new char[n];
for (int i = 0; i < n; i++) {
result[i] = ' ';
}
return result;
}
public void reset() {
buffer.setLength(0);
}
public void syncLineNumbers(TreeNode node) {
if (emitLineDirectives) {
int sourceLine = node.getLineNumber();
if (sourceLine > 0 && currentLine != sourceLine) {
buffer.append(UnicodeUtils.format("\n#line %d\n", sourceLine));
currentLine = sourceLine;
}
}
}
/**
* Emits a #line directive setting the given filename.
* @param fileName the filename to sync
*/
public void syncFilename(String fileName) {
if (emitLineDirectives) {
if (!fileName.equals(currentFile)) {
currentLine = BEGINNING_OF_FILE;
// C11 spec. (6.10.4) requires a line number between 1 and 2147483647.
buffer.append(UnicodeUtils.format("\n#line 1 \"%s\"\n", fileName));
}
}
currentFile = fileName;
}
/**
* Fix line indention, based on brace count.
*/
public String reindent(String code) {
try {
// Remove indention from each line.
StringBuffer sb = new StringBuffer();
LineReader lr = new LineReader(new StringReader(code));
String line = lr.readLine();
while (line != null) {
sb.append(line.trim());
line = lr.readLine();
if (line != null) {
sb.append('\n');
}
}
String strippedCode = sb.toString();
// Now indent it again.
int indent = indention * DEFAULT_INDENTION;
sb.setLength(0); // reset buffer
lr = new LineReader(new StringReader(strippedCode));
line = lr.readLine();
while (line != null) {
if (line.startsWith("}")) {
indent -= DEFAULT_INDENTION;
}
if (!line.startsWith("#line")) {
sb.append(pad(indent));
}
sb.append(line);
if (line.endsWith("{")) {
indent += DEFAULT_INDENTION;
}
line = lr.readLine();
if (line != null) {
sb.append('\n');
}
}
return sb.toString();
} catch (IOException e) {
// Should never happen with string readers.
throw new AssertionError(e);
}
}
public int getCurrentLine() {
return currentLine;
}
}