77
88import static com .adacore .liblkqllang .Liblkqllang .*;
99
10- import java . io . PrintWriter ;
11- import java . nio . charset . StandardCharsets ;
10+ import com . adacore . lkql_jit . options . Refactorings . LKQLToLkt ;
11+ import com . adacore . lkql_jit . options . Refactorings . Refactoring ;
1212import java .util .ArrayList ;
13- import java .util .HashMap ;
1413import java .util .List ;
1514import java .util .concurrent .Callable ;
16- import java .util .function .Consumer ;
17- import java .util .function .Predicate ;
1815import picocli .CommandLine ;
1916
2017/**
@@ -53,6 +50,7 @@ public class LKQLRefactor implements Callable<Integer> {
5350
5451 private enum refactoringKind {
5552 IS_TO_COLON ,
53+ TO_LKQL_V2 ,
5654 }
5755
5856 @ CommandLine .Option (
@@ -62,202 +60,46 @@ private enum refactoringKind {
6260 )
6361 private refactoringKind refactoring ;
6462
65- /** Kind of actions that can be attached to a token. */
66- private enum actionKind {
67- APPEND ,
68- PREPEND ,
69- REPLACE ,
70- }
71-
72- /**
73- * Action record, encapsulating an action that can be attached to a token. When applied, the
74- * action will either append the given text, prepend the given text, or replace the token text
75- * with the given text, depending on the kind.
76- *
77- * <p>To remove a token, simply use a REPLACE action with an empty text.
78- */
79- private record Action (actionKind kind , String text ) {}
80-
81- /**
82- * Global map of actions that will be applied on tokens. To append an action, use the addAction
83- * method.
84- */
85- private final HashMap <String , List <Action >> actions = new HashMap <>();
86-
87- /**
88- * Helper to create a unique Id for a token. TODO: This method exists only because the
89- * 'hashCode' method on Tokens is wrong. This should be fixed at the Langkit level. See
90- * eng/libadalang/langkit#857.
91- *
92- * @return the id as a string
93- */
94- private String getTokenId (Token token ) {
95- return (
96- token .unit .getFileName () +
97- (token .isTrivia () ? "trivia " + token .triviaIndex : token .tokenIndex ) +
98- token .kind
99- );
100- }
101-
102- /** Add an action to the list of actions to apply */
103- public void addAction (Token token , Action action ) {
104- List <Action > actionList ;
105- var tokenId = getTokenId (token );
106- if (actions .containsKey (tokenId )) {
107- actionList = actions .get (tokenId );
108- } else {
109- actionList = new ArrayList <>();
110- actions .put (tokenId , actionList );
111- }
112-
113- actionList .add (action );
114- }
115-
116- /**
117- * Helper for refactor writers: Returns the first token that satisfies the predicate 'pred',
118- * iterating on tokens from 'fromTok' (including 'fromTok').
119- *
120- * <p>If no token is found, return the null token.
121- */
122- public static Token firstWithPred (Token fromTok , Predicate <Token > pred ) {
123- var curTok = fromTok ;
124- while (!curTok .isNone () && !pred .test (curTok )) {
125- curTok = curTok .next ();
126- }
127- return curTok ;
128- }
129-
130- /**
131- * Internal method: rewrite a unit by printing all the tokens via the 'write' callback. Apply
132- * the actions at the same time.
133- */
134- private void printAllTokens (AnalysisUnit unit , Consumer <String > write ) {
135- for (var tok = unit .getFirstToken (); !tok .isNone (); tok = tok .next ()) {
136- var tokActions = actions .get (getTokenId (tok ));
137- if (tokActions != null ) {
138- var replaceActions = tokActions
139- .stream ()
140- .filter (c -> c .kind == actionKind .REPLACE )
141- .toList ();
142- assert replaceActions .size () <= 1 : "Only one replace action per token" ;
143-
144- var prependActions = tokActions
145- .stream ()
146- .filter (c -> c .kind == actionKind .PREPEND )
147- .toList ();
148-
149- var appendActions = tokActions
150- .stream ()
151- .filter (c -> c .kind == actionKind .APPEND )
152- .toList ();
153-
154- for (var action : prependActions ) {
155- write .accept (action .text );
156- }
157-
158- if (!replaceActions .isEmpty ()) {
159- write .accept (replaceActions .get (0 ).text );
160- } else {
161- write .accept (tok .getText ());
162- }
163-
164- for (var action : appendActions ) {
165- write .accept (action .text );
166- }
167- } else {
168- write .accept (tok .getText ());
169- }
170- }
171- }
172-
173- /**
174- * Internal method: rewrite a unit by printing all the tokens, either to stdout, or to the
175- * original file, depending on the value of the '-i' command line flag.
176- */
177- private void printAllTokens (AnalysisUnit unit ) {
178- try {
179- if (this .inPlace ) {
180- var writer = new PrintWriter (unit .getFileName (), StandardCharsets .UTF_8 );
181- printAllTokens (unit , writer ::print );
182- writer .close ();
183- } else {
184- printAllTokens (unit , System .out ::print );
185- }
186- } catch (Exception e ) {
187- throw new RuntimeException (e );
188- }
189- }
190-
191- /**
192- * Helper for findAll. Visit all children of 'node', calling 'cons' on each of them. TODO: Hoist
193- * in Java bindings (see eng/libadalang/langkit#859)
194- */
195- private static void visitChildren (LkqlNode node , Consumer <LkqlNode > cons ) {
196- if (node == null || node .isNone ()) {
197- return ;
198- }
199-
200- for (var c : node .children ()) {
201- if (c != null && !c .isNone ()) {
202- cons .accept (c );
203- visitChildren (c , cons );
204- }
205- }
206- }
207-
208- /**
209- * Helper for refactor writers: Find all nodes that are children of root and which satisfies the
210- * predicate 'pred'. TODO: Hoist in Java bindings (see eng/libadalang/langkit#859)
211- */
212- public static List <LkqlNode > findAll (LkqlNode root , Predicate <LkqlNode > pred ) {
213- var result = new ArrayList <LkqlNode >();
214- visitChildren (root , c -> {
215- if (pred .test (c )) {
216- result .add (c );
217- }
218- });
219- return result ;
220- }
221-
22263 /**
22364 * Return the refactoring corresponding to enum value passed on command line. This is where
22465 * concrete refactorings are implemented.
22566 */
226- public Consumer < AnalysisUnit > getRefactoring () {
227- switch (refactoring ) {
228- case IS_TO_COLON :
229- return unit -> {
230- for ( var det : findAll ( unit . getRoot (), n -> n instanceof NodePatternDetail )) {
231- var tokIs = firstWithPred (
232- det . tokenStart (),
233- t -> t . kind == TokenKind . LKQL_IS
234- );
235-
236- if (! tokIs . isNone ()) {
237- // Replace "is" -> ":"
238- addAction ( tokIs , new Action ( actionKind . REPLACE , ":" ));
239-
240- // Get rid of previous token if it is a whitespace
241- var prev = tokIs . previous ();
242- if ( prev . kind == TokenKind . LKQL_WHITESPACE ) {
243- addAction ( tokIs . previous (), new Action ( actionKind . REPLACE , "" ));
244- }
67+ public Refactoring getRefactoring (AnalysisUnit unit ) {
68+ 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+ );
77+
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 ());
24586 }
24687 }
247- };
248- }
249- return null ;
88+ }
89+ };
90+ case TO_LKQL_V2 -> LKQLToLkt .instantiate ();
91+ };
25092 }
25193
25294 @ Override
25395 public Integer call () {
254- var refactoring = getRefactoring ();
25596 var ctx = AnalysisContext .create ();
25697 for (var file : files ) {
25798 var unit = ctx .getUnitFromFile (file );
258- refactoring .accept (unit );
259- printAllTokens (unit );
260- actions .clear ();
99+ var refactoring = getRefactoring (unit );
100+ var state = new Refactoring .State (unit );
101+ refactoring .applyRefactor (state );
102+ state .printAllTokens (this .inPlace );
261103 }
262104 return 0 ;
263105 }
0 commit comments