1 /*
  2  * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 package hat.tools.textmodel;
 26 
 27 import jdk.incubator.code.dialect.core.CoreOp;
 28 import hat.tools.textmodel.tokens.Arrow;
 29 import hat.tools.textmodel.tokens.At;
 30 import hat.tools.textmodel.tokens.Ch;
 31 import hat.tools.textmodel.tokens.DottedName;
 32 import hat.tools.textmodel.tokens.FloatConst;
 33 import hat.tools.textmodel.tokens.IntConst;
 34 import hat.tools.textmodel.tokens.LeafReplacementToken;
 35 import hat.tools.textmodel.tokens.LineCol;
 36 import hat.tools.textmodel.tokens.Parenthesis;
 37 import hat.tools.textmodel.tokens.ReservedWord;
 38 import hat.tools.textmodel.tokens.Seq;
 39 import hat.tools.textmodel.tokens.StringLiteral;
 40 import hat.tools.textmodel.tokens.Token;
 41 import hat.tools.textmodel.tokens.Ws;
 42 
 43 import java.io.IOException;
 44 import java.nio.file.Files;
 45 import java.nio.file.Path;
 46 import java.util.ArrayList;
 47 import java.util.HashMap;
 48 import java.util.List;
 49 import java.util.Map;
 50 import java.util.function.Predicate;
 51 import java.util.regex.Matcher;
 52 import java.util.regex.Pattern;
 53 import java.util.stream.Collectors;
 54 
 55 public class BabylonTextModel extends TextModel {
 56 
 57     public static class BabylonTypeAttribute extends LeafReplacementToken {
 58         public BabylonTypeAttribute(Token l, Token m, Token r) {
 59             super(l, m, r);
 60         }
 61     }
 62 
 63     public static class BabylonRefAttribute extends LeafReplacementToken {
 64         public BabylonRefAttribute(Token l, Token m, Token r) {
 65             super(l, m, r);
 66         }
 67     }
 68 
 69     public static class BabylonNamedAttribute extends LeafReplacementToken {
 70         public final String name;
 71 
 72         public BabylonNamedAttribute(Token l, Token lm, Token rm, Token r) {
 73             super(l, lm, rm, r);
 74             this.name = l.asString();
 75         }
 76     }
 77 
 78     public static class BabylonLocationAttribute extends BabylonNamedAttribute implements LineCol {
 79         static Pattern locPattern = Pattern.compile("\"([0-9]+):([0-9]+)[^\"]*\"");
 80         public final int line;
 81         public final int col;
 82 
 83         public BabylonLocationAttribute(Token l, Token lm, Token rm, Token r) {
 84             super(l, lm, rm, r);
 85             if (locPattern.matcher(r.asString()) instanceof Matcher m && m.matches() && m.groupCount() > 1) {
 86                 line = Integer.parseInt(m.group(1));
 87                 col = Integer.parseInt(m.group(2));
 88             } else {
 89                 throw new IllegalArgumentException("invalid location attribute no line/col");
 90             }
 91         }
 92 
 93         @Override
 94         public int line() {
 95             return line;
 96         }
 97 
 98         @Override
 99         public int col() {
100             return col;
101         }
102     }
103 
104     public static class BabylonFileLocationAttribute extends BabylonLocationAttribute {
105         static Pattern locFilePattern = Pattern.compile("\"([0-9]+):([0-9]+):file:([^\"]*)\"");
106         final Path path;
107 
108         static Path getPathFromFileLocString(String fileLocString) {
109             return locFilePattern.matcher(fileLocString) instanceof Matcher m
110                     && m.matches()
111                     && m.groupCount() > 2
112                     && m.group(3) instanceof String filename
113                     && Path.of(filename) instanceof Path javaSource
114                     ? javaSource : null;
115         }
116 
117         public BabylonFileLocationAttribute(Token l, Token lm, Token rm, Token r) {
118             super(l, lm, rm, r);
119             this.path = getPathFromFileLocString(r.asString());
120         }
121     }
122 
123     public static class BabylonAnonymousAttribute extends LeafReplacementToken {
124         public BabylonAnonymousAttribute(Token l, Token r) {
125             super(l, r);
126         }
127     }
128 
129     public static class BabylonSSARef extends LeafReplacementToken {
130         public final int id;
131 
132         public BabylonSSARef(Token t1, Token intConst) {
133             super(t1, intConst);
134             this.id = ((IntConst) intConst).i;
135         }
136 
137         public static boolean isA(Token t, Predicate<BabylonSSARef> predicate) {
138             return t instanceof BabylonSSARef ssaRef && predicate.test(ssaRef);
139         }
140 
141         public static boolean isA(Token t) {
142             return isA(t, _ -> true);
143         }
144     }
145 
146     public static class BabylonBlock extends LeafReplacementToken {
147         static final public Pattern regex = Pattern.compile("block_([0-9]+)");
148 
149         public final int id;
150 
151         public BabylonBlock(Token t1, Token t2) {
152             super(t1, t2);
153             if (regex.matcher(t2.asString()) instanceof Matcher m && m.matches() && m.groupCount() == 1) {
154                 id = Integer.parseInt(m.group(1));
155             } else {
156                 throw new IllegalArgumentException("invalid block attribute no id");
157             }
158         }
159 
160         public static boolean isA(Token t, Predicate<Token> predicate) {
161             return t instanceof BabylonBlock && predicate.test(t);
162         }
163 
164         public static boolean isA(Token t) {
165             return isA(t, _ -> true);
166         }
167     }
168 
169     public static class BabylonBlockDef extends LeafReplacementToken {
170         public final int id;
171 
172         public BabylonBlockDef(Token ref) {
173             super(ref);
174             this.id = ((BabylonBlock) ref).id;
175         }
176     }
177 
178     public static class BabylonSSADef extends LeafReplacementToken {
179         public final int id;
180 
181         public BabylonSSADef(Token ssaRef) {
182             super(ssaRef);
183             this.id = ((BabylonSSARef) ssaRef).id;
184         }
185     }
186 
187     public static class BabylonOp extends LeafReplacementToken {
188         public static final Pattern regex = Pattern.compile(
189                 "(field|var)\\.(store|load)|var|return|yield|continue|invoke|conv|mul|div|add|sub|constant|mod|lt"
190         );
191 
192         public BabylonOp(Token t) {
193             super(t);
194         }
195     }
196 
197     public static class BabylonBlockOrBody extends LeafReplacementToken {
198         public static final Pattern regex = Pattern.compile("java\\.(if|while)");
199 
200         public BabylonBlockOrBody(Token t) {
201             super(t);
202         }
203     }
204 
205     public Path javaSource;
206     public JavaTextModel javaTextModel;
207 
208     public record SSAEdge(BabylonSSARef ssaRef, BabylonSSADef ssaDef) {
209     }
210 
211     public record BlockEdge(BabylonBlock ref, BabylonBlockDef def) {
212     }
213 
214     public List<SSAEdge> ssaEdgeList = new ArrayList<>();
215     public List<BlockEdge> blockEdgeList = new ArrayList<>();
216     public Map<Integer, BabylonSSADef> idToSSADefMap = new HashMap<>();
217     public Map<Integer, BabylonBlockDef> idToBlockDefMap = new HashMap<>();
218     public List<BabylonLocationAttribute> babylonLocationAttributes = new ArrayList<>();
219 
220     private BabylonTextModel transform() {
221         // "[0-9][0-9]*" ->IntConst
222         replace(true, t -> Seq.isA(t, $ -> $.matches(IntConst.regex)), IntConst::new);
223 
224         // IntConst '.' IntConst ->FloatConst   (yeah we are missing '.' IntConst  and the exponent stuff)
225         replace(true, (t1, t2, t3) -> IntConst.isA(t1) && Ch.isADot(t2) && Seq.isA(t3), FloatConst::new);
226 
227         // (Seq|Dname) '.' Seq -> Dname
228         replace(true, (t1, t2, t3) -> (Seq.isA(t1) || DottedName.isA(t1)) && Ch.isADot(t2) && Seq.isA(t3), DottedName::new);
229 
230         // map all seqs to DottedName
231         replace(true, t -> Seq.isA(t, $ -> $.matches(DottedName.regex)), DottedName::new);
232 
233         Pattern reservedWords = Pattern.compile("(func|Var)");
234         // reserved word -> ReservedWord
235         replace(true, t -> DottedName.isA(t, $ -> $.matches(reservedWords)), ReservedWord::new);
236 
237 
238         // ^block_[0-9]+ -> Block
239         replace(true, (t1, t2) -> Ch.isAHat(t1) && DottedName.isA(t2, $ -> $.matches(BabylonBlock.regex)), BabylonBlock::new);
240 
241         // ^block_[0-9]+: -> BlockDef
242         replace(true, t -> BabylonBlock.isA(t, $ -> $.next(Ch::isAColon)), BabylonBlockDef::new);
243 
244         // ^block_[0-9]+() -> Block
245         // This is broken just because we have a '(' does not make it a def we also need to check for the colon
246         replace(true, t -> BabylonBlock.isA(t, $ -> $.next2((t2,t3)->t2 instanceof Parenthesis && Ch.isAColon(t3))), BabylonBlockDef::new);
247 
248 
249         // various opnames -> Op  (I am sure I have missed some)
250         replace(true, t -> DottedName.isA(t, $ -> $.matches(BabylonOp.regex)), BabylonOp::new);
251 
252         // java.while || java.if -> Body
253         replace(true, t -> DottedName.isA(t, $ -> $.matches(BabylonBlockOrBody.regex)), BabylonBlockOrBody::new);
254 
255         // '-' + '>' -> ->
256         replace(true, (t1, t2) -> Ch.isAHyphen(t1) && Ch.isAGt(t2), Arrow::new);
257 
258 
259         // java.type:"MyTypename" -> Type
260         replace(true, (t1, t2, t3) ->
261                         DottedName.isA(t1, $ -> $.is("java.type")) && Ch.isAColon(t2) && StringLiteral.isA(t3)
262                 , BabylonTypeAttribute::new
263         );
264 
265         // java.ref:"MyTypename" -> Type
266         replace(true, (t1, t2, t3) ->
267                         DottedName.isA(t1, $ -> $.is("java.ref")) && Ch.isAColon(t2) && StringLiteral.isA(t3)
268                 , BabylonRefAttribute::new
269         );
270 
271         // %[0-9]+ -> BabylonSSARef
272         replace(true, (t1, t2) -> Ch.isAPercent(t1) && IntConst.isA(t2), BabylonSSARef::new);
273 
274         // We separate SSARefs and SSADefs
275         // SSARef : -> SSADef
276         // otherwise we leave as a SSARef
277         replace(true, t -> BabylonSSARef.isA(t,
278                         $ -> $.next2((t2, t3) -> Ws.isA(t2) && Ch.isAColon(t3)) // this is a lookahead.. t2 and t3 are not replaced
279                 )
280                 , BabylonSSADef::new
281         );
282 
283         // @ (char) -> At
284         replace(true, Ch::isAnAt, At::new);
285 
286         //  @"foo" -> AnonymousAttribute
287         replace(true, (t1, t2) -> At.isA(t1) && StringLiteral.isA(t2), BabylonAnonymousAttribute::new);
288 
289         //  @loc="line:col:file.*" -> FileLocationAttribute
290         replace(true, (t1, t2, t3, t4) ->
291                         At.isA(t1)
292                                 && DottedName.isA(t2, $ -> $.is("loc"))
293                                 && Ch.isAnEquals(t3)
294                                 && StringLiteral.isA(t4, $ -> $.matches(BabylonFileLocationAttribute.locFilePattern))
295                 , BabylonFileLocationAttribute::new
296         );
297 
298         //  @loc="line:col:.*" -> LocationAttribute
299         replace(true, (t1, t2, t3, t4) ->
300                         At.isA(t1)
301                                 && DottedName.isA(t2, $ -> $.is("loc"))
302                                 && Ch.isAnEquals(t3)
303                                 && StringLiteral.isA(t4, $ -> $.matches(BabylonLocationAttribute.locPattern))
304                 , BabylonLocationAttribute::new
305         );
306         //  @name=".*" -> LocationAttribute
307         replace(true, (t1, t2, t3, t4) ->
308                         At.isA(t1) && DottedName.isA(t2) && Ch.isAnEquals(t3) && StringLiteral.isA(t4)
309                 , BabylonNamedAttribute::new
310         );
311 
312         visit(t -> {
313             if (t instanceof BabylonSSADef def) {
314                 idToSSADefMap.put(def.id, def);
315             } else if (t instanceof BabylonSSARef ref) {
316                 if (!idToSSADefMap.containsKey(ref.id)) {
317                     throw new IllegalArgumentException("Unknown possibly forward BabylonSSARef id " + ref.id);
318                 }
319                 var def = idToSSADefMap.get(ref.id);
320                 ssaEdgeList.add(new SSAEdge(ref, def));
321             } else if (t instanceof BabylonLocationAttribute loc) {
322                 babylonLocationAttributes.add(loc);
323             } else if (t instanceof BabylonBlockDef def) {
324                 idToBlockDefMap.put(def.id, def);
325             }
326         });
327         visit(t -> {
328                     if (t instanceof BabylonBlock ref) {
329                         if (!idToBlockDefMap.containsKey(ref.id)) {
330                             throw new IllegalArgumentException("Unknown possibly forward BabylonBlock id " + ref.id);
331                         }
332                         var def = idToBlockDefMap.get(ref.id);
333                         blockEdgeList.add(new BlockEdge(ref, def));
334                     }
335                 }
336         );
337 
338         babylonLocationAttributes = babylonLocationAttributes.stream().sorted().collect(Collectors.toList());
339         return this;
340     }
341 
342     static public BabylonTextModel of(String text) {
343         BabylonTextModel doc = new BabylonTextModel();
344         doc.parse(text);
345         doc.find(true, (t) -> t instanceof StringLiteral, (t) -> {
346             if (t instanceof StringLiteral sl
347                     && sl.matcher(BabylonFileLocationAttribute.locFilePattern) instanceof Matcher m
348                     && Path.of(m.group(3)) instanceof Path javaSource && Files.exists(javaSource)
349             ) {
350                 doc.javaSource = javaSource;
351                 try {
352                     doc.javaTextModel = JavaTextModel.of(Files.readString(javaSource));
353                 } catch (IOException e) {
354                     throw new RuntimeException(e);
355                 }
356             }
357         });
358         if (doc.javaSource == null) {
359             throw new IllegalStateException("No source!");
360         }
361         doc.transform();
362         return doc;
363     }
364 
365     static public BabylonTextModel of(CoreOp.FuncOp javaFunc) {
366         var crDoc = of(javaFunc.toText());
367         return crDoc;
368     }
369 }