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 }