Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package org.example.ebnfFormatter.render;

record OriginalGap(String text, boolean hasComment) {
}
69 changes: 69 additions & 0 deletions src/main/java/org/example/ebnfFormatter/render/OriginalGaps.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.example.ebnfFormatter.render;

import com.github.javaparser.JavaToken;
import com.github.javaparser.TokenRange;
import com.github.javaparser.ast.Node;

import java.util.Optional;

final class OriginalGaps {
private OriginalGaps() {
}

static Optional<OriginalGap> between(Object left, Object right) {
Optional<TokenRange> leftRange = tokenRange(left);
Optional<TokenRange> rightRange = tokenRange(right);
if (leftRange.isEmpty() || rightRange.isEmpty()) {
return Optional.empty();
}

JavaToken end = rightRange.get().getBegin();
Optional<JavaToken> current = leftRange.get().getEnd().getNextToken();
StringBuilder text = new StringBuilder();
boolean hasComment = false;

while (current.isPresent() && current.get() != end) {
JavaToken token = current.get();
if (!token.getCategory().isWhitespaceOrComment()) {
return Optional.empty();
}
hasComment = hasComment || token.getCategory().isComment();
text.append(token.getText());
current = token.getNextToken();
}

return current.isPresent() ? Optional.of(new OriginalGap(text.toString(), hasComment)) : Optional.empty();
}

static Optional<OriginalGap> after(Object value) {
Optional<TokenRange> range = tokenRange(value);
if (range.isEmpty()) {
return Optional.empty();
}

Optional<JavaToken> current = range.get().getEnd().getNextToken();
StringBuilder text = new StringBuilder();
boolean hasComment = false;

while (current.isPresent() && current.get().getCategory().isWhitespaceOrComment()) {
JavaToken token = current.get();
hasComment = hasComment || token.getCategory().isComment();
text.append(token.getText());
current = token.getNextToken();
}

return Optional.of(new OriginalGap(text.toString(), hasComment));
}

private static Optional<TokenRange> tokenRange(Object value) {
if (value instanceof Node node) {
return node.getTokenRange();
}

if (value instanceof Optional<?> optional) {
return optional.flatMap(OriginalGaps::tokenRange);
}

return Optional.empty();
}
}
171 changes: 154 additions & 17 deletions src/main/java/org/example/ebnfFormatter/render/RenderContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,84 @@ public final class RenderContext {
private static final String INDENT_UNIT = " ";

private final StringBuilder out = new StringBuilder();
private final StringBuilder pendingWhitespace = new StringBuilder();
private int indentLevel = 0;
private boolean lineStart = true;
private int column = 0;

public void appendText(String text) {
if (text == null || text.isEmpty()) {
return;
}

for (int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
flushPendingWhitespace();
appendIndentedText(text);
}

if (lineStart) {
appendIndentIfNeeded();
}
public void appendRawText(String text) {
if (text == null || text.isEmpty()) {
return;
}

out.append(ch);
flushPendingWhitespace();
appendRawCommitted(text);
}

if (ch == '\n') {
lineStart = true;
} else {
lineStart = false;
}
void appendSourceWhitespace(String text) {
if (text == null || text.isEmpty()) {
return;
}

flushPendingWhitespace();
appendWhitespace(text);
}

public void space() {
void appendSourceTokenText(String text) {
if (text == null || text.isEmpty()) {
return;
}

flushPendingWhitespace();
if (lineStart) {
appendIndentIfNeeded();
}
out.append(' ');
lineStart = false;
appendRawCommitted(text);
}

public void replacePendingWhitespaceWithRaw(String text) {
if (startsOnPendingLine(text)) {
flushPendingWhitespace();
appendIndentedText(text);
return;
}

pendingWhitespace.setLength(0);
appendRawCommitted(text);
}

public void appendPendingWhitespace(String text) {
if (text == null || text.isEmpty()) {
return;
}

pendingWhitespace.append(text);
}

public void flushPendingWhitespace() {
if (pendingWhitespace.isEmpty()) {
return;
}

appendWhitespace(pendingWhitespace.toString());
pendingWhitespace.setLength(0);
}

public void space() {
appendPendingWhitespace(" ");
}

public void newline() {
out.append('\n');
lineStart = true;
appendPendingWhitespace("\n");
}

public void indent() {
Expand All @@ -53,14 +95,109 @@ public void dedent() {
indentLevel--;
}

public boolean isLineStart() {
if (pendingWhitespace.isEmpty()) {
return lineStart;
}

char last = pendingWhitespace.charAt(pendingWhitespace.length() - 1);
return last == '\n' || last == '\r';
}

int sourceStartColumn() {
flushPendingWhitespace();
if (lineStart) {
return currentIndentColumns();
}
return column;
}

int currentIndentColumns() {
return indentLevel * INDENT_UNIT.length();
}

public String result() {
flushPendingWhitespace();
return out.toString();
}

private void appendIndentedText(String text) {
for (int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);

if (lineStart) {
appendIndentIfNeeded();
}

out.append(ch);

if (ch == '\n') {
lineStart = true;
column = 0;
} else {
lineStart = false;
column++;
}
}
}

private void appendWhitespace(String text) {
for (int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
if (ch == '\n' || ch == '\r') {
out.append(ch);
lineStart = true;
column = 0;
continue;
}

if (lineStart) {
appendIndentIfNeeded();
}
out.append(ch);
lineStart = false;
column++;
}
}

private void appendRawCommitted(String text) {
if (text == null || text.isEmpty()) {
return;
}

for (int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
out.append(ch);
if (ch == '\n' || ch == '\r') {
lineStart = true;
column = 0;
} else {
lineStart = false;
column++;
}
}
}

private boolean startsOnPendingLine(String text) {
return !pendingWhitespace.isEmpty()
&& isLineStart()
&& startsWithoutIndent(text);
}

private boolean startsWithoutIndent(String text) {
return text != null
&& !text.isEmpty()
&& text.charAt(0) != ' '
&& text.charAt(0) != '\t'
&& text.charAt(0) != '\n'
&& text.charAt(0) != '\r';
}

private void appendIndentIfNeeded() {
for (int i = 0; i < indentLevel; i++) {
out.append(INDENT_UNIT);
column += INDENT_UNIT.length();
}
lineStart = false;
}
}
}
Loading
Loading