55
66package com .adacore .lkql_jit ;
77
8+ import com .adacore .langkit_support .LangkitSupport ;
89import com .adacore .liblkqllang .Liblkqllang ;
10+ import com .adacore .liblktlang .Liblktlang ;
911import com .adacore .lkql_jit .checker .utils .CheckerUtils ;
1012import com .adacore .lkql_jit .exception .LKQLRuntimeException ;
1113import com .adacore .lkql_jit .langkit_translator .passes .FramingPass ;
14+ import com .adacore .lkql_jit .langkit_translator .passes .LktPasses ;
1215import com .adacore .lkql_jit .langkit_translator .passes .ResolutionPass ;
1316import com .adacore .lkql_jit .langkit_translator .passes .TranslationPass ;
1417import com .adacore .lkql_jit .langkit_translator .passes .framing_utils .ScriptFrames ;
2629import com .oracle .truffle .api .source .Source ;
2730import java .io .PrintStream ;
2831import java .nio .charset .StandardCharsets ;
32+ import java .util .Scanner ;
2933import org .graalvm .options .OptionCategory ;
3034import org .graalvm .options .OptionDescriptors ;
3135import org .graalvm .options .OptionKey ;
@@ -103,6 +107,7 @@ public final class LKQLLanguage extends TruffleLanguage<LKQLContext> {
103107 static final OptionKey <String > options = new OptionKey <>("" );
104108
105109 Liblkqllang .AnalysisContext lkqlAnalysisContext ;
110+ Liblktlang .AnalysisContext lktAnalysisContext ;
106111
107112 // ----- Constructors -----
108113
@@ -121,6 +126,14 @@ public LKQLLanguage() {
121126 1
122127 );
123128
129+ this .lktAnalysisContext = Liblktlang .AnalysisContext .create (
130+ null ,
131+ null ,
132+ null ,
133+ null ,
134+ true ,
135+ 1
136+ );
124137 // Set the color support flag
125138 SUPPORT_COLOR = System .getenv ("TERM" ) != null && System .console () != null ;
126139 }
@@ -205,44 +218,8 @@ protected CallTarget parse(ParsingRequest request) {
205218 final Liblkqllang .AnalysisUnit unit ;
206219 TopLevelList result ;
207220
208- if (request .getSource ().getPath () == null ) {
209- unit = lkqlAnalysisContext .getUnitFromBuffer (
210- request .getSource ().getCharacters ().toString (),
211- "<command-line>"
212- );
213- } else {
214- unit = lkqlAnalysisContext .getUnitFromFile (request .getSource ().getPath ());
215- }
216-
217- // Verify the parsing result
218- final var diagnostics = unit .getDiagnostics ();
219- if (diagnostics .length > 0 ) {
220- var ctx = LKQLLanguage .getContext (null );
221-
222- // Iterate over diagnostics
223- for (Liblkqllang .Diagnostic diagnostic : diagnostics ) {
224- ctx
225- .getDiagnosticEmitter ()
226- .emitDiagnostic (
227- CheckerUtils .MessageKind .ERROR ,
228- diagnostic .message .toString (),
229- null ,
230- SourceSectionWrapper .create (
231- diagnostic .sourceLocationRange ,
232- request .getSource ()
233- )
234- );
235- }
236- throw LKQLRuntimeException .fromMessage (
237- "Syntax errors in " + unit .getFileName (false ) + ": stopping interpreter"
238- );
239- }
240-
241- // Get the LKQL langkit AST
242- final Liblkqllang .TopLevelList lkqlLangkitRoot = (Liblkqllang .TopLevelList ) unit .getRoot ();
243-
244221 // Translate the LKQL AST from Langkit to a Truffle AST
245- result = (TopLevelList ) translate (lkqlLangkitRoot , request .getSource ());
222+ result = (TopLevelList ) translate (request .getSource ());
246223
247224 // If the current parsing request is the root request
248225 if (!request .getSource ().isInternal ()) {
@@ -273,22 +250,14 @@ protected CallTarget parse(ParsingRequest request) {
273250 return new TopLevelRootNode (request .getSource ().isInternal (), result , this ).getCallTarget ();
274251 }
275252
276- /** Translate the given source from string. */
277- public LKQLNode translate (String source , String sourceName ) {
278- Source src = Source .newBuilder (Constants .LKQL_ID , source , sourceName ).build ();
279- var root = lkqlAnalysisContext .getUnitFromBuffer (source , sourceName ).getRoot ();
280- return translate (root , src , false );
281- }
282-
283253 /**
284- * Translate the given source Langkit AST.
254+ * Private helper. Translate the given source Langkit AST to LKQLNode hierarchy .
285255 *
286- * @param lkqlLangkitRoot The LKQL Langkit AST to translate.
287- * @param source The Truffle source of the AST.
288- * @return The translated LKQL Truffle AST.
256+ * <p>This method works on LKQL roots and Lkt roots, dispatching on the proper parser and
257+ * translation pass as needed.
289258 */
290- public LKQLNode translate (
291- final Liblkqllang . LkqlNode lkqlLangkitRoot ,
259+ private LKQLNode translate (
260+ final LangkitSupport . NodeInterface root ,
292261 final Source source ,
293262 boolean isPrelude
294263 ) {
@@ -301,11 +270,11 @@ public LKQLNode translate(
301270 PRELUDE_SOURCE ,
302271 "<prelude>"
303272 ).build ();
304- var root = lkqlAnalysisContext
273+ var preludeRoot = lkqlAnalysisContext
305274 .getUnitFromBuffer (PRELUDE_SOURCE , "<prelude>" )
306275 .getRoot ();
307- var preludeRoot = (TopLevelList ) translate (root , preludeSource , true );
308- var callTarget = new TopLevelRootNode (true , preludeRoot , this ).getCallTarget ();
276+ var lkqlPrelude = (TopLevelList ) translate (preludeRoot , preludeSource , true );
277+ var callTarget = new TopLevelRootNode (true , lkqlPrelude , this ).getCallTarget ();
309278 global .prelude = (LKQLNamespace ) callTarget .call ();
310279 var preludeMap = global .prelude .asMap ();
311280
@@ -320,27 +289,143 @@ public LKQLNode translate(
320289 }
321290 }
322291
323- // Do the framing pass to create the script frame descriptions
324- final FramingPass framingPass = new FramingPass (source );
325- lkqlLangkitRoot .accept (framingPass );
326- final ScriptFrames scriptFrames = framingPass
327- .getScriptFramesBuilder ()
328- .build (CONTEXT_REFERENCE .get (null ).getGlobal ());
292+ if (root instanceof Liblkqllang .LkqlNode lkqlRoot ) {
293+ // Do the framing pass to create the script frame descriptions
294+ final FramingPass framingPass = new FramingPass (source );
295+ lkqlRoot .accept (framingPass );
296+ final ScriptFrames scriptFrames = framingPass
297+ .getScriptFramesBuilder ()
298+ .build (CONTEXT_REFERENCE .get (null ).getGlobal ());
299+
300+ // Do the translation pass and return the result
301+ final TranslationPass translationPass = new TranslationPass (source , scriptFrames );
302+
303+ final var ast = lkqlRoot .accept (translationPass );
304+ if (!isPrelude ) {
305+ final var resolutionPass = new ResolutionPass ();
306+ resolutionPass .passEntry (ast );
307+ }
308+ return ast ;
309+ } else if (root instanceof Liblktlang .LangkitRoot lktRoot ) {
310+ final ScriptFrames frames = LktPasses .Frames .buildFrames (lktRoot ).build (
311+ CONTEXT_REFERENCE .get (null ).getGlobal ()
312+ );
329313
330- // Do the translation pass and return the result
331- final TranslationPass translationPass = new TranslationPass (source , scriptFrames );
314+ final var ast = LktPasses .buildLKQLNode (source , lktRoot , frames );
315+ if (!isPrelude ) {
316+ final var resolutionPass = new ResolutionPass ();
317+ resolutionPass .passEntry (ast );
318+ }
319+ return ast ;
320+ } else {
321+ throw LKQLRuntimeException .fromMessage ("Should not happen" );
322+ }
323+ }
332324
333- final var ast = lkqlLangkitRoot .accept (translationPass );
325+ /**
326+ * Translate the given source Langkit AST. The source can be either legacy LKQL syntax (LKQL V1)
327+ * or Lkt syntax (future LKQL v2).
328+ *
329+ * <p>The default is LKQL syntax, but either syntaxes can be triggered by a comment in the first
330+ * line of the file: # lkql version: 1/2
331+ *
332+ * <p>The trigger is a simple string match, so the comment needs to match exactly that. We might
333+ * relax those constraints at a later stage.
334+ *
335+ * @param source The Truffle source of the AST.
336+ * @return The translated LKQL Truffle AST.
337+ */
338+ public LKQLNode translate (final Source source , String sourceName ) {
339+ var firstLine = new Scanner (source .getReader ()).nextLine ();
340+ LangkitSupport .AnalysisContextInterface langkitCtx = null ;
341+ var baseName = source .getName ().replaceFirst ("[.][^.]+$" , "" );
342+ Source src ;
343+
344+ if (getContext (null ).getOptions ().autoTranslateUnits ().contains (baseName )) {
345+ System .out .println ("Translating " + source .getName () + " to lkt" );
346+ String newSrc = source .getCharacters ().toString ();
347+ var lines = newSrc .split ("\n " );
348+
349+ if (firstLine .startsWith ("# lkql version:" )) {
350+ if (firstLine .equals ("# lkql version: 1" )) {
351+ // Translate LKQL to Lkt (LKQL v2)
352+ newSrc = String .join ("\n " , Arrays .stream (lines ).skip (1 ).toList ());
353+ } else {
354+ throw LKQLRuntimeException .fromMessage (
355+ "Invalid lkql version line for autoTranslateUnit unit"
356+ );
357+ }
358+ }
334359
335- if (!isPrelude ) {
336- final var resolutionPass = new ResolutionPass ();
337- resolutionPass .passEntry (ast );
360+ var lktSrc = LKQLToLkt .lkqlToLkt (sourceName , newSrc );
361+
362+ if (getContext (null ).isVerbose ()) {
363+ System .out .println ("Lkt source for " + sourceName );
364+ System .out .println ("=============================" );
365+ System .out .println (lktSrc );
366+ System .out .println ();
367+ }
368+
369+ // Build a new source
370+ src = Source .newBuilder (Constants .LKQL_ID , lktSrc , source .getName ()).build ();
371+
372+ langkitCtx = lktAnalysisContext ;
373+ } else if (firstLine .startsWith ("# lkql version:" )) {
374+ if (firstLine .equals ("# lkql version: 1" )) {
375+ // lkql V1 uses lkql syntax
376+ langkitCtx = lkqlAnalysisContext ;
377+ } else if (firstLine .equals ("# lkql version: 2" )) {
378+ // lkql V2 uses Lkt syntax
379+ langkitCtx = lktAnalysisContext ;
380+ } else {
381+ throw LKQLRuntimeException .fromMessage ("Invalid lkql version" );
382+ }
383+ } else {
384+ // By default, use lkql syntax
385+ langkitCtx = lkqlAnalysisContext ;
386+ }
387+
388+ LangkitSupport .AnalysisUnit unit ;
389+ if (source .getPath () == null ) {
390+ unit = langkitCtx .getUnitFromBuffer (source .getCharacters ().toString (), sourceName );
391+ } else {
392+ unit = langkitCtx .getUnitFromFile (source .getPath ());
338393 }
339394
340- return ast ;
395+ final var diagnostics = unit .getDiagnostics ();
396+ if (diagnostics .length > 0 ) {
397+ var ctx = LKQLLanguage .getContext (null );
398+
399+ // Iterate over diagnostics
400+ for (var diagnostic : diagnostics ) {
401+ ctx
402+ .getDiagnosticEmitter ()
403+ .emitDiagnostic (
404+ CheckerUtils .MessageKind .ERROR ,
405+ diagnostic .getMessage ().toString (),
406+ null ,
407+ SourceSectionWrapper .create (diagnostic .getSourceLocationRange (), source )
408+ );
409+ }
410+ throw LKQLRuntimeException .fromMessage (
411+ "Syntax errors in " + unit .getFileName (false ) + ": stopping interpreter"
412+ );
413+ }
414+
415+ return translate (unit .getRoot (), source , false );
416+ }
417+
418+ /**
419+ * Shortcut to translate a source. If it has no name, it will be given the name
420+ * "<command-line>".
421+ */
422+ public LKQLNode translate (final Source source ) {
423+ return translate (source , "<command-line>" );
341424 }
342425
343- public LKQLNode translate (final Liblkqllang .LkqlNode lkqlLangkitRoot , final Source source ) {
344- return translate (lkqlLangkitRoot , source , false );
426+ /** Shortcut to translate the given source from string. */
427+ public LKQLNode translate (String source , String sourceName ) {
428+ Source src = Source .newBuilder (Constants .LKQL_ID , source , sourceName ).build ();
429+ return translate (src , sourceName );
345430 }
346431}
0 commit comments