diff --git a/src/main/java/org/example/ebnfFormatter/render/TemplateRenderer.java b/src/main/java/org/example/ebnfFormatter/render/TemplateRenderer.java index 8b4e905..682b0bd 100644 --- a/src/main/java/org/example/ebnfFormatter/render/TemplateRenderer.java +++ b/src/main/java/org/example/ebnfFormatter/render/TemplateRenderer.java @@ -1,17 +1,11 @@ package org.example.ebnfFormatter.render; +import com.github.javaparser.JavaToken; +import com.github.javaparser.TokenRange; import com.github.javaparser.ast.Modifier; import com.github.javaparser.ast.Node; -import com.github.javaparser.ast.NodeList; -import com.github.javaparser.ast.body.MethodDeclaration; -import com.github.javaparser.ast.expr.AssignExpr; -import com.github.javaparser.ast.expr.BinaryExpr; -import com.github.javaparser.ast.expr.Expression; -import com.github.javaparser.ast.expr.UnaryExpr; -import com.github.javaparser.ast.stmt.*; -import com.github.javaparser.ast.type.PrimitiveType; -import com.github.javaparser.metamodel.PropertyMetaModel; import com.github.javaparser.printer.Stringable; +import com.github.javaparser.printer.lexicalpreservation.LexicalPreservingPrinter; import org.example.ebnfFormatter.match.AppliedRuleValue; import org.example.ebnfFormatter.match.Bindings; import org.example.ebnfFormatter.match.BoundValue; @@ -96,13 +90,93 @@ private void renderJoin( RenderContext context ) { List items = bindings.findValues(join.placeholderName()); + boolean hasEmptySeparator = isEmptyFormat(join.separator()); for (int i = 0; i < items.size(); i++) { + BoundValue item = items.get(i); if (i > 0) { - renderInto(join.separator(), bindings, nestedRuleRenderer, context); + if (hasEmptySeparator) { + appendOriginalGapBetween(items.get(i - 1), item, context); + } else { + renderInto(join.separator(), bindings, nestedRuleRenderer, context); + } + } + renderBoundValue(join.placeholderName(), item, nestedRuleRenderer, context); + } + + if (hasEmptySeparator && !items.isEmpty()) { + appendOriginalGapAfter(items.getLast(), context); + } + } + + private boolean isEmptyFormat(FormatAst format) { + return switch (format) { + case FormatText text -> text.text().isEmpty(); + case FormatSeq seq -> seq.items().stream().allMatch(this::isEmptyFormat); + case FormatGroup group -> isEmptyFormat(group.body()); + default -> false; + }; + } + + private void appendOriginalGapBetween(BoundValue left, BoundValue right, RenderContext context) { + originalGapBetween(left.legacyValue(), right.legacyValue()).ifPresent(context::appendText); + } + + private void appendOriginalGapAfter(BoundValue value, RenderContext context) { + originalGapAfter(value.legacyValue()).ifPresent(context::appendText); + } + + private Optional originalGapBetween(Object left, Object right) { + Optional leftRange = tokenRange(left); + Optional rightRange = tokenRange(right); + if (leftRange.isEmpty() || rightRange.isEmpty()) { + return Optional.empty(); + } + + JavaToken end = rightRange.get().getBegin(); + Optional current = leftRange.get().getEnd().getNextToken(); + StringBuilder text = new StringBuilder(); + + while (current.isPresent() && current.get() != end) { + JavaToken token = current.get(); + if (!token.getCategory().isWhitespaceOrComment()) { + return Optional.empty(); } - renderBoundValue(join.placeholderName(), items.get(i), nestedRuleRenderer, context); + text.append(token.getText()); + current = token.getNextToken(); + } + + return current.isPresent() ? Optional.of(text.toString()) : Optional.empty(); + } + + private Optional originalGapAfter(Object value) { + Optional range = tokenRange(value); + if (range.isEmpty()) { + return Optional.empty(); + } + + Optional current = range.get().getEnd().getNextToken(); + StringBuilder text = new StringBuilder(); + + while (current.isPresent() && current.get().getCategory().isWhitespaceOrComment()) { + JavaToken token = current.get(); + text.append(token.getText()); + current = token.getNextToken(); + } + + return Optional.of(text.toString()); + } + + private Optional tokenRange(Object value) { + if (value instanceof Node node) { + return node.getTokenRange(); + } + + if (value instanceof Optional optional) { + return optional.flatMap(this::tokenRange); } + + return Optional.empty(); } private void renderBoundValue( @@ -224,7 +298,16 @@ private void renderNode(Node node, NestedRuleRenderer nestedRuleRenderer, Render return; } - context.appendText(node.toString()); + context.appendText(sourceText(node)); + } + + private String sourceText(Node node) { + if (LexicalPreservingPrinter.isAvailableOn(node)) { // может быть полезно для маштабирования + return LexicalPreservingPrinter.print(node); + } + return node.getTokenRange() + .map(TokenRange::toString) + .orElseGet(() -> LexicalPreservingPrinter.print(node)); } private String stripQuantifierSuffix(String name) { diff --git a/src/test/java/org/example/ebnfFormatter/runtime/ALotOfEndToEndTest.java b/src/test/java/org/example/ebnfFormatter/runtime/ALotOfEndToEndTest.java index 4d2ae9b..c78e00f 100644 --- a/src/test/java/org/example/ebnfFormatter/runtime/ALotOfEndToEndTest.java +++ b/src/test/java/org/example/ebnfFormatter/runtime/ALotOfEndToEndTest.java @@ -18,7 +18,7 @@ public class ALotOfEndToEndTest { - private static final String ALL_RULES = """ + private static final String ALL_RULES = """ ::= CompilationUnit(packageDeclaration?=, imports=[*], types=[*]) => ifpresent(PackageDeclaration, nl nl) ifpresent(ImportDeclaration, join(, nl) nl nl) @@ -70,7 +70,7 @@ public class ALotOfEndToEndTest { ::= => sp ; - + ::= IfStmt(condition=, thenStmt=, elseStmt?=) => "if" sp "(" ")" ifpresent(ElseStmt, nl "else" ); @@ -189,7 +189,7 @@ public class MathBox{public static int sum(int a,int b){return a+b;}} """ public class MathBox { public static int sum(int a, int b) { - return a + b; + return a+b; } }""" ); @@ -223,7 +223,7 @@ class Branches{int max(int a,int b){if(a>b)return a;else return b;}} """ class Branches { int max(int a, int b) { - if (a > b) + if (a>b) return a; else return b; @@ -241,9 +241,9 @@ class Branches{int choose(int a,int b){if(a>b)return a;else if(a==b)return 0;els """ class Branches { int choose(int a, int b) { - if (a > b) + if (a>b) return a; - else if (a == b) + else if (a==b) return 0; else return b; @@ -261,7 +261,7 @@ class Branches{int max(int a,int b){if(a>b){a++;return a;}else{b++;return b;}}} """ class Branches { int max(int a, int b) { - if (a > b) { + if (a>b) { a++; return a; } @@ -299,7 +299,7 @@ class Loop{void run(){for(i=0,j=1;i<10;i++,j++)step();}} """ class Loop { void run() { - for (i = 0, j = 1; i < 10; i++, j++) + for (i=0, j=1; i<10; i++, j++) step(); } }""" @@ -315,7 +315,7 @@ class Loop{void run(){for(i=0,j=1;;i++,j++)step();}} """ class Loop { void run() { - for (i = 0, j = 1;; i++, j++) + for (i=0, j=1;; i++, j++) step(); } }""" @@ -331,7 +331,7 @@ class Loop{void run(){for(i=0;i<3;i++){step();step();}}} """ class Loop { void run() { - for (i = 0; i < 3; i++) { + for (i=0; i<3; i++) { step(); step(); } @@ -367,7 +367,7 @@ class Counter{int run(){int x=1;x++;return x;}} """ class Counter { int run() { - int x = 1; + int x=1; x++; return x; } @@ -484,8 +484,8 @@ class Complex{int run(int x){if(x>0)for(i=0;i<3;i++)tick();else return x;}} """ class Complex { int run(int x) { - if (x > 0) - for (i = 0; i < 3; i++) + if (x>0) + for (i=0; i<3; i++) tick(); else return x; @@ -503,10 +503,10 @@ class Complex{int run(int x){if(x>0)return x;else for(i=0;i<2;i++){tick();tick() """ class Complex { int run(int x) { - if (x > 0) + if (x>0) return x; else - for (i = 0; i < 2; i++) { + for (i=0; i<2; i++) { tick(); tick(); } @@ -524,8 +524,8 @@ class Scanner{int scan(int limit){for(i=0;i0){for(i=0;i 0) { - for (i = 0; i < a; i++) + if (a>0) { + for (i=0; i 1) + if (a>1) return a; else return 1; @@ -622,14 +622,14 @@ void boot() { } int choose(int a, int b) { - if (a > b) + if (a>b) return a; else return b; } void spin() { - for (i = 0; i < 3; i++) + for (i=0; i<3; i++) tick(); } }""" @@ -652,9 +652,9 @@ int id() { class Second { int pick(int a, int b, int c) { - if (a > b) + if (a>b) return a; - else if (b > c) + else if (b>c) return b; else return c; @@ -683,7 +683,7 @@ class Empty {} class Worker { void go() { - for (i = 0; i < 1; i++) { + for (i=0; i<1; i++) { step(); } } @@ -700,16 +700,16 @@ class Deep{int run(int a,int b){if(a>b){for(i=0;i b) { - for (i = 0; i < a; i++) { - if (i == b) + if (a>b) { + for (i=0; i b) { - while (i < a) { - if (i == b) - return i; - tick(); - ++i; - } + while (i < a) { + if (i == b) + return i;tick();++i; + } return a; } else if (a == b) { diff --git a/src/test/java/org/example/ebnfFormatter/runtime/ExamplesFromDocumentationTest.java b/src/test/java/org/example/ebnfFormatter/runtime/ExamplesFromDocumentationTest.java index c86a695..e157408 100644 --- a/src/test/java/org/example/ebnfFormatter/runtime/ExamplesFromDocumentationTest.java +++ b/src/test/java/org/example/ebnfFormatter/runtime/ExamplesFromDocumentationTest.java @@ -104,7 +104,7 @@ public int sum(int a, int b) { String expected = """ if (a == b) return 2 * a; - else if ((a & 2) == 2) + else if ((a&2)==2) return a + b; else return b;"""; @@ -133,13 +133,7 @@ public class AST { } """; - String expected = """ - public int sum(int a, int b) { - if (a == b) { - return 2 * a; - } - return a + b; - }"""; + String expected = "public int sum(int a, int b) {if(a==b){return 2*a;}return a+b;}"; String formatted = formatFirstNode(methodDeclarationRules, code, MethodDeclaration.class, "MethodDeclaration"); assertThat(formatted).isEqualTo(expected); @@ -153,9 +147,7 @@ public int sum(Parameter a,Parameter b,Parameter c,Parameter d) {} } """; - String expected = """ - public int sum(Parameter a, Parameter b, Parameter c, Parameter d) { - }"""; + String expected = "public int sum(Parameter a, Parameter b, Parameter c, Parameter d) {}"; String formatted = formatFirstNode(methodDeclarationRules, code, MethodDeclaration.class, "MethodDeclaration"); assertThat(formatted).isEqualTo(expected); @@ -170,7 +162,9 @@ public abstract int sum(Input } """; - String expected = "public abstract int sum(Input input)"; + String expected = """ + public abstract int sum(Input + input)"""; String formatted = formatFirstNode(methodDeclarationRules, code, MethodDeclaration.class, "MethodDeclaration"); assertThat(formatted).isEqualTo(expected); @@ -210,7 +204,7 @@ public int sum(int a, int b) { """; String expected = """ - for (int i = 0; i < 5; ++i) { + for (int i=0; i<5; ++i) { sm += i; }"""; @@ -230,7 +224,7 @@ public int sum(int a, int b) { """; String expected = """ - for (int i = 0; i < 5; ++i) + for (int i=0; i<5; ++i) sm += i;"""; String formatted = formatFirstNode(forStmtRules, code, ForStmt.class, "ForStmt"); diff --git a/src/test/java/org/example/ebnfFormatter/runtime/FormatterEngineE2ETest.java b/src/test/java/org/example/ebnfFormatter/runtime/FormatterEngineE2ETest.java index eb59897..6de3bec 100644 --- a/src/test/java/org/example/ebnfFormatter/runtime/FormatterEngineE2ETest.java +++ b/src/test/java/org/example/ebnfFormatter/runtime/FormatterEngineE2ETest.java @@ -67,7 +67,7 @@ void formats_if_without_else() { String actual = engine.format(node, "ifRule"); - assertThat(actual).isEqualTo("if (a > b) return a;"); + assertThat(actual).isEqualTo("if (a>b) return a;"); } @Test diff --git a/src/test/java/org/example/ebnfFormatter/runtime/LikeReadmeNodeFormattingE2ETest.java b/src/test/java/org/example/ebnfFormatter/runtime/LikeReadmeNodeFormattingE2ETest.java index aa925fa..d4c5654 100644 --- a/src/test/java/org/example/ebnfFormatter/runtime/LikeReadmeNodeFormattingE2ETest.java +++ b/src/test/java/org/example/ebnfFormatter/runtime/LikeReadmeNodeFormattingE2ETest.java @@ -68,7 +68,7 @@ public abstract int sum(Input\s } """; - String expected = "public abstract int sum(Input input)"; + String expected = "public abstract int sum(Input \n input)"; MethodDeclaration node = StaticJavaParser.parse(source) .findFirst(MethodDeclaration.class) diff --git a/src/test/java/org/example/ebnfFormatter/runtime/UnknownNodesFallbackEndToEndTest.java b/src/test/java/org/example/ebnfFormatter/runtime/UnknownNodesFallbackEndToEndTest.java index 4fb1733..9f9bfdd 100644 --- a/src/test/java/org/example/ebnfFormatter/runtime/UnknownNodesFallbackEndToEndTest.java +++ b/src/test/java/org/example/ebnfFormatter/runtime/UnknownNodesFallbackEndToEndTest.java @@ -4,7 +4,6 @@ import com.github.javaparser.ParseResult; import com.github.javaparser.ParserConfiguration; import com.github.javaparser.ast.CompilationUnit; -import com.github.javaparser.ast.stmt.Statement; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import org.example.ebnfFormatter.dsl.RuleAstBuilder; @@ -206,7 +205,7 @@ class Sample { void run() { %s } - }""".formatted(indent(parseStatement(statement).toString(), 8)); + }""".formatted(indent(statement, 8)); assertThat(formatASTFromRootToLeafs(code)).isEqualTo(expected); } @@ -229,12 +228,6 @@ private static CompilationUnit parseCompilationUnit(String code) { .orElseThrow(() -> new IllegalArgumentException(result.getProblems().toString())); } - private static Statement parseStatement(String statement) { - ParseResult result = javaParser.parseStatement(statement); - return result.getResult() - .orElseThrow(() -> new IllegalArgumentException(result.getProblems().toString())); - } - private static RuleRegistry registryWithRules() { RuleRegistry ruleRegistry = new RuleRegistry(); ruleRegistry.registerAll(ruleDefs); diff --git a/src/test/java/org/example/ebnfFormatter/runtime/UnknownNodesWholeFileEndToEndTest.java b/src/test/java/org/example/ebnfFormatter/runtime/UnknownNodesWholeFileEndToEndTest.java index 7a34562..a35258f 100644 --- a/src/test/java/org/example/ebnfFormatter/runtime/UnknownNodesWholeFileEndToEndTest.java +++ b/src/test/java/org/example/ebnfFormatter/runtime/UnknownNodesWholeFileEndToEndTest.java @@ -154,9 +154,7 @@ class Flow{void run(){while(irun();case C->stop();default->r """ class Switcher { void run() { - switch(mode) { - case A, B -> - run(); - case C -> - stop(); - default -> - reset(); - } + switch(mode){case A,B->run();case C->stop();default->reset();} done(); } }""" @@ -214,13 +202,7 @@ class Worker{void run(){try{work();}catch(IllegalArgumentException|IllegalStateE """ class Worker { void run() { - try { - work(); - } catch (IllegalArgumentException | IllegalStateException e) { - handle(e); - } finally { - cleanup(); - } + try{work();}catch(IllegalArgumentException|IllegalStateException e){handle(e);}finally{cleanup();} done(); } }""" @@ -236,9 +218,7 @@ class Reader{void run(){try(Input input=open()){read(input);}closeCount++;}} """ class Reader { void run() { - try (Input input = open()) { - read(input); - } + try(Input input=open()){read(input);} closeCount++; } }""" @@ -254,10 +234,8 @@ class Guarded{void run(){synchronized(lock){work();}assert ready:"not ready";don """ class Guarded { void run() { - synchronized (lock) { - work(); - } - assert ready : "not ready"; + synchronized(lock){work();} + assert ready:"not ready"; done(); } }""" @@ -273,9 +251,7 @@ class Iteration{void run(){for(String item:items){use(item);}done();}} """ class Iteration { void run() { - for (String item : items) { - use(item); - } + for(String item:items){use(item);} done(); } }""" @@ -291,8 +267,8 @@ class LocalStuff{void run(){Runnable task=()->work();java.util.function.Function """ class LocalStuff { void run() { - Runnable task = () -> work(); - java.util.function.Function trim = String::trim; + Runnable task=()->work(); + java.util.function.Function trim=String::trim; task.run(); } }""" @@ -325,14 +301,7 @@ class Branch{void run(){if(ready)while(running){tick();}else do{sleep();}while(w """ class Branch { void run() { - if (ready) - while (running) { - tick(); - } - else - do { - sleep(); - } while (waiting); + if(ready)while(running){tick();}else do{sleep();}while(waiting); done(); } }""" @@ -348,12 +317,7 @@ class Mixed{void run(){for(i=0;istart();default->ti """ class Mixed { void run() { - for (i = 0; i < limit; i++) switch(i) { - case 0 -> - start(); - default -> - tick(); - } + for(i=0;istart();default->tick();} done(); } }""" @@ -375,18 +339,11 @@ class Mixed{void loop(){while((line=reader.readLine())!=null){process(line);}}vo class Mixed { void loop() { - while ((line = reader.readLine()) != null) { - process(line); - } + while((line=reader.readLine())!=null){process(line);} } void choose() { - switch(mode) { - case A, B -> - run(); - default -> - reset(); - } + switch(mode){case A,B->run();default->reset();} } }""" );