|
9 | 9 |
|
10 | 10 | import com.adacore.lkql_jit.options.Refactorings.LKQLToLkt; |
11 | 11 | import com.adacore.lkql_jit.options.Refactorings.Refactoring; |
| 12 | +import com.adacore.lkql_jit.options.Refactorings.TokenBasedRefactoring; |
| 13 | +import java.io.PrintWriter; |
| 14 | +import java.nio.charset.StandardCharsets; |
12 | 15 | import java.util.ArrayList; |
13 | 16 | import java.util.List; |
14 | 17 | import java.util.concurrent.Callable; |
15 | 18 | import picocli.CommandLine; |
16 | 19 |
|
17 | 20 | /** |
18 | | - * Refactor command for LKQL. Allows to run automatic migrations, allowing the LKQL team to create |
19 | | - * migrators to adapt to syntactic or semantic changes in LKQL. |
| 21 | + * Refactor command for LKQL. Allows to run automatic migrations, allowing the |
| 22 | + * LKQL team to create migrators to adapt to syntactic or semantic changes in |
| 23 | + * LKQL. |
20 | 24 | * |
21 | | - * <p>Migrators work on the stream of tokens directly. Migrator implementer will attach actions |
22 | | - * (append/prepend/replace) on tokens, which allows to modify the output stream without working on |
23 | | - * text directly, which simplifies the expression of refactorings. |
| 25 | + * <p> |
| 26 | + * Migrators are just an implementation of the functionnal interface |
| 27 | + * 'Refactoring'. It expects an LKQL unit as input and produces the new unit |
| 28 | + * content as a 'String'. |
24 | 29 | * |
25 | | - * <p>A migrator implementer will typically: |
| 30 | + * <p> |
| 31 | + * In practice, a 'Refactoring' implementation will be of one of 2 kinds: |
| 32 | + * 1. A 'TokenBasedRefactoring' working on the stream of tokens directly and |
| 33 | + * producing rewriting actions on each token of the stream. |
| 34 | + * 2. A 'TreeBasedRefactoring' working on the Liblkqllang syntax tree and |
| 35 | + * producing a new 'String' directly. |
26 | 36 | * |
27 | | - * <p>1. Find the nodes he wants to refactor in the LKQL tree 2. From those nodes, find the tokens |
28 | | - * inside the nodes that need to be altered 3. Attach actions to those tokens |
| 37 | + * <p> |
| 38 | + * Then the rewriter will emit a new file for every LKQL unit, either inplace or |
| 39 | + * on stdout. |
29 | 40 | * |
30 | | - * <p>Then the rewriter will emit a new file for every LKQL unit, either inplace or on stdout. |
| 41 | + * <p> |
| 42 | + * To add a refactoring, you need to extend the 'RefactoringKind' enum to add a |
| 43 | + * new refactoring id, and then extend the 'getRefactoring' method to return the |
| 44 | + * corresponding implementation of 'Refactoring' to apply. |
31 | 45 | * |
32 | | - * <p>To add a refactoring, you need to extend the 'refactoringKind' enum to add a new refactoring |
33 | | - * id, and then extend the 'getRefactoring' method to return an anonymous function that will add |
34 | | - * actions to the list of actions. |
35 | 46 | */ |
36 | 47 | @CommandLine.Command( |
37 | 48 | name = "refactor", |
@@ -66,40 +77,49 @@ private enum RefactoringKind { |
66 | 77 | */ |
67 | 78 | public Refactoring getRefactoring(AnalysisUnit unit) { |
68 | 79 | return switch (refactoring) { |
69 | | - case IS_TO_COLON -> (Refactoring.State state) -> { |
70 | | - for (var det : Refactoring.findAll(unit.getRoot(), n -> |
71 | | - n instanceof NodePatternDetail |
72 | | - )) { |
73 | | - var tokIs = Refactoring.firstWithPred( |
74 | | - det.tokenStart(), |
75 | | - t -> t.kind == TokenKind.LKQL_IS |
76 | | - ); |
| 80 | + case IS_TO_COLON -> (TokenBasedRefactoring) state -> { |
| 81 | + Refactoring.stream(state.unit.getRoot()) |
| 82 | + .filter(n -> n instanceof NodePatternDetail) |
| 83 | + .forEach(det -> { |
| 84 | + Refactoring.streamFrom(det.tokenStart()) |
| 85 | + .filter(t -> t.kind == TokenKind.LKQL_IS) |
| 86 | + .findFirst() |
| 87 | + .ifPresent(tokIs -> { |
| 88 | + // Replace "is" -> ":" |
| 89 | + state.replace(tokIs, ":"); |
77 | 90 |
|
78 | | - if (!tokIs.isNone()) { |
79 | | - // Replace "is" -> ":" |
80 | | - state.replace(tokIs, ":"); |
81 | | - |
82 | | - // Get rid of previous token if it is a whitespace |
83 | | - var prev = tokIs.previous(); |
84 | | - if (Refactoring.isWhitespace(prev)) { |
85 | | - state.delete(tokIs.previous()); |
86 | | - } |
87 | | - } |
88 | | - } |
| 91 | + // Get rid of previous token if it is a whitespace |
| 92 | + var prev = tokIs.previous(); |
| 93 | + if (Refactoring.isWhitespace(prev)) { |
| 94 | + state.delete(prev); |
| 95 | + } |
| 96 | + }); |
| 97 | + }); |
89 | 98 | }; |
90 | | - case TO_LKQL_V2 -> LKQLToLkt.instantiate(); |
| 99 | + case TO_LKQL_V2 -> new LKQLToLkt(); |
91 | 100 | }; |
92 | 101 | } |
93 | 102 |
|
| 103 | + /** |
| 104 | + * Rewrite a unit by printing refactored file, either to stdout, or to the |
| 105 | + * original file, depending on the value of the '-i' command line flag. |
| 106 | + */ |
94 | 107 | @Override |
95 | 108 | public Integer call() { |
96 | 109 | var ctx = AnalysisContext.create(); |
97 | 110 | for (var file : files) { |
98 | 111 | var unit = ctx.getUnitFromFile(file); |
99 | | - var refactoring = getRefactoring(unit); |
100 | | - var state = new Refactoring.State(unit); |
101 | | - refactoring.applyRefactor(state); |
102 | | - state.printAllTokens(this.inPlace); |
| 112 | + var result = getRefactoring(unit).apply(unit); |
| 113 | + |
| 114 | + if (inPlace) { |
| 115 | + try (var writer = new PrintWriter(unit.getFileName(), StandardCharsets.UTF_8)) { |
| 116 | + writer.print(result); |
| 117 | + } catch (Exception e) { |
| 118 | + throw new RuntimeException(e); |
| 119 | + } |
| 120 | + } else { |
| 121 | + System.out.print(result); |
| 122 | + } |
103 | 123 | } |
104 | 124 | return 0; |
105 | 125 | } |
|
0 commit comments