Skip to content

Commit ea6f4d5

Browse files
raph-amiardKillian Perlin
authored andcommitted
Introduce the lkql -> lkt syntax translator
1 parent efbaff3 commit ea6f4d5

File tree

11 files changed

+441
-205
lines changed

11 files changed

+441
-205
lines changed

lkql_jit/cli/src/main/java/com/adacore/lkql_jit/cli/BaseLKQLChecker.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ protected LKQLOptions.Builder getBaseOptionsBuilder() {
130130
.projectFile(this.args.project)
131131
.rulesDir(this.args.rulesDirs)
132132
.ruleInstances(getRuleInstances())
133-
.keepGoingOnMissingFile(this.args.keepGoingOnMissingFile);
133+
.keepGoingOnMissingFile(this.args.keepGoingOnMissingFile)
134+
.autoTranslateUnits(this.args.autoTranslateUnits);
134135
}
135136

136137
/** Get the rule instances defined be the user through the LKQL checker command-line. */
@@ -187,6 +188,12 @@ public abstract static class Args implements Callable<Integer> {
187188
@CommandLine.Spec
188189
public picocli.CommandLine.Model.CommandSpec spec;
189190

191+
@CommandLine.Option(
192+
names = { "--to-lkt" },
193+
description = "List of units to auto translate to Lkt"
194+
)
195+
public List<String> autoTranslateUnits = new ArrayList<>();
196+
190197
@CommandLine.Option(names = { "-v", "--verbose" }, description = "Enable the verbose mode")
191198
public boolean verbose;
192199

lkql_jit/cli/src/main/java/com/adacore/lkql_jit/cli/LKQLLauncher.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ public static class LKQLRun implements Callable<Integer> {
4646
)
4747
public String charset = null;
4848

49+
@CommandLine.Option(
50+
names = { "--to-lkt" },
51+
description = "List of units to auto translate to Lkt"
52+
)
53+
public List<String> autoTranslateUnits = new ArrayList<>();
54+
4955
@CommandLine.Option(names = { "-P", "--project" }, description = "Project file to use")
5056
public String project = null;
5157

@@ -164,15 +170,16 @@ protected int executeScript(Context.Builder contextBuilder) {
164170
.runtime(this.args.RTS)
165171
.keepGoingOnMissingFile(this.args.keepGoingOnMissingFile)
166172
.files(this.args.files)
167-
.charset(this.args.charset);
173+
.charset(this.args.charset)
174+
.autoTranslateUnits(this.args.autoTranslateUnits);
168175

169176
// Finally, pass the options to the LKQL engine
170177
contextBuilder.option("lkql.options", optionsBuilder.build().toJson().toString());
171178

172179
// Create the context and run the script in it
173180
try (Context context = contextBuilder.build()) {
174181
if (this.args.script != null) {
175-
final Source source = Source.newBuilder("lkql", new File(this.args.script)).build();
182+
Source source = Source.newBuilder("lkql", new File(this.args.script)).build();
176183
context.eval(source);
177184
}
178185

lkql_jit/cli/src/main/java/com/adacore/lkql_jit/cli/LKQLRefactor.java

Lines changed: 30 additions & 188 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,11 @@
77

88
import 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;
1212
import java.util.ArrayList;
13-
import java.util.HashMap;
1413
import java.util.List;
1514
import java.util.concurrent.Callable;
16-
import java.util.function.Consumer;
17-
import java.util.function.Predicate;
1815
import 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
}

lkql_jit/language/src/main/java/com/adacore/lkql_jit/LKQLLanguage.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.adacore.lkql_jit.nodes.TopLevelList;
2020
import com.adacore.lkql_jit.nodes.root_nodes.TopLevelRootNode;
2121
import com.adacore.lkql_jit.options.LKQLOptions;
22+
import com.adacore.lkql_jit.options.Refactorings.LKQLToLkt;
2223
import com.adacore.lkql_jit.runtime.GlobalScope;
2324
import com.adacore.lkql_jit.runtime.values.LKQLNamespace;
2425
import com.adacore.lkql_jit.utils.Constants;
@@ -29,6 +30,7 @@
2930
import com.oracle.truffle.api.source.Source;
3031
import java.io.PrintStream;
3132
import java.nio.charset.StandardCharsets;
33+
import java.util.Arrays;
3234
import java.util.Scanner;
3335
import org.graalvm.options.OptionCategory;
3436
import org.graalvm.options.OptionDescriptors;
@@ -374,22 +376,25 @@ public LKQLNode translate(final Source source, String sourceName) {
374376
if (firstLine.equals("# lkql version: 1")) {
375377
// lkql V1 uses lkql syntax
376378
langkitCtx = lkqlAnalysisContext;
379+
src = source;
377380
} else if (firstLine.equals("# lkql version: 2")) {
378381
// lkql V2 uses Lkt syntax
379382
langkitCtx = lktAnalysisContext;
383+
src = source;
380384
} else {
381385
throw LKQLRuntimeException.fromMessage("Invalid lkql version");
382386
}
383387
} else {
384388
// By default, use lkql syntax
385389
langkitCtx = lkqlAnalysisContext;
390+
src = source;
386391
}
387392

388393
LangkitSupport.AnalysisUnit unit;
389-
if (source.getPath() == null) {
390-
unit = langkitCtx.getUnitFromBuffer(source.getCharacters().toString(), sourceName);
394+
if (src.getPath() == null) {
395+
unit = langkitCtx.getUnitFromBuffer(src.getCharacters().toString(), sourceName);
391396
} else {
392-
unit = langkitCtx.getUnitFromFile(source.getPath());
397+
unit = langkitCtx.getUnitFromFile(src.getPath());
393398
}
394399

395400
final var diagnostics = unit.getDiagnostics();
@@ -404,23 +409,23 @@ public LKQLNode translate(final Source source, String sourceName) {
404409
CheckerUtils.MessageKind.ERROR,
405410
diagnostic.getMessage().toString(),
406411
null,
407-
SourceSectionWrapper.create(diagnostic.getSourceLocationRange(), source)
412+
SourceSectionWrapper.create(diagnostic.getSourceLocationRange(), src)
408413
);
409414
}
410415
throw LKQLRuntimeException.fromMessage(
411416
"Syntax errors in " + unit.getFileName(false) + ": stopping interpreter"
412417
);
413418
}
414419

415-
return translate(unit.getRoot(), source, false);
420+
return translate(unit.getRoot(), src, false);
416421
}
417422

418423
/**
419424
* Shortcut to translate a source. If it has no name, it will be given the name
420425
* "<command-line>".
421426
*/
422427
public LKQLNode translate(final Source source) {
423-
return translate(source, "<command-line>");
428+
return translate(source, source.getName());
424429
}
425430

426431
/** Shortcut to translate the given source from string. */

lkql_jit/options/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@
3838
<artifactId>launcher-common</artifactId>
3939
<version>${graalvm.version}</version>
4040
</dependency>
41+
42+
<dependency>
43+
<groupId>com.adacore</groupId>
44+
<artifactId>liblkqllang</artifactId>
45+
<version>${config.liblkqllangVersion}</version>
46+
</dependency>
4147
</dependencies>
4248

4349
</project>

0 commit comments

Comments
 (0)