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 
 26 package hat.tools.text;
 27 
 28 import java.io.*;
 29 
 30 import jdk.incubator.code.*;
 31 import jdk.incubator.code.dialect.java.JavaType;
 32 import jdk.incubator.code.dialect.java.WildcardType;
 33 import jdk.incubator.code.extern.ExternalizedOp;
 34 import jdk.incubator.code.extern.ExternalizedTypeElement;
 35 
 36 import java.lang.reflect.Array;
 37 import java.nio.charset.StandardCharsets;
 38 import java.util.HashMap;
 39 import java.util.List;
 40 import java.util.Map;
 41 import java.util.function.Consumer;
 42 import java.util.function.Function;
 43 import java.util.stream.Collectors;
 44 import java.util.stream.IntStream;
 45 
 46 /**
 47  * A writer of code models to the textual form.
 48  * <p>
 49  * A code model in textual form may be parsed back into the runtime form by parsing it.
 50  */
 51 public final class OpCodeBuilder {
 52 
 53     // Hacked from jdk.incubator.code/share/classes/jdk/incubator/code/dialect/java/impl/JavaTypeUtils.java
 54 static class JavaTypeUtils{
 55 
 56     // useful type identifiers
 57 
 58     /**  Inflated Java class type name */
 59     public static final String JAVA_TYPE_CLASS_NAME = "java.type.class";
 60     /**  Inflated Java array type name */
 61     public static final String JAVA_TYPE_ARRAY_NAME = "java.type.array";
 62     /**  Inflated Java wildcard type name */
 63     public static final String JAVA_TYPE_WILDCARD_NAME = "java.type.wildcard";
 64     /**  Inflated Java type var name */
 65     public static final String JAVA_TYPE_VAR_NAME = "java.type.var";
 66     /**  Inflated Java primitive type name */
 67     public static final String JAVA_TYPE_PRIMITIVE_NAME = "java.type.primitive";
 68 
 69     /** Inflated Java field reference name */
 70     public static final String JAVA_REF_FIELD_NAME = "java.ref.field";
 71     /** Inflated Java method reference name */
 72     public static final String JAVA_REF_METHOD_NAME = "java.ref.method";
 73     /** Inflated Java constructor reference name */
 74     public static final String JAVA_REF_CONSTRUCTOR_NAME = "java.ref.constructor";
 75     /** Inflated Java record name */
 76     public static final String JAVA_REF_RECORD_NAME = "java.ref.record";
 77 
 78     /** Flattened Java type name */
 79     public static final String JAVA_TYPE_FLAT_NAME_PREFIX = "java.type:";
 80     /** Flattened Java reference name */
 81     public static final String JAVA_REF_FLAT_NAME_PREFIX = "java.ref:";
 82 
 83     /**
 84      * An enum modelling the Java type form kind. Useful for switching.
 85      */
 86     public enum Kind {
 87         /** A flattened type form */
 88         FLATTENED_TYPE,
 89         /** A flattened reference form */
 90         FLATTENED_REF,
 91         /** An inflated type form */
 92         INFLATED_TYPE,
 93         /** An inflated reference form */
 94         INFLATED_REF,
 95         /** Some other form */
 96         OTHER;
 97 
 98         /**
 99          * Constructs a new kind from an externalized type form
100          * @param tree the externalized type form
101          * @return the kind modelling {@code tree}
102          */
103         public static Kind of(ExternalizedTypeElement tree) {
104             return switch (tree.identifier()) {
105                 case JAVA_TYPE_CLASS_NAME, JAVA_TYPE_ARRAY_NAME,
106                      JAVA_TYPE_PRIMITIVE_NAME, JAVA_TYPE_WILDCARD_NAME,
107                      JAVA_TYPE_VAR_NAME -> INFLATED_TYPE;
108                 case JAVA_REF_FIELD_NAME, JAVA_REF_CONSTRUCTOR_NAME,
109                      JAVA_REF_METHOD_NAME, JAVA_REF_RECORD_NAME -> INFLATED_REF;
110                 case String s when s.startsWith(JAVA_TYPE_FLAT_NAME_PREFIX) -> FLATTENED_TYPE;
111                 case String s when s.startsWith(JAVA_REF_FLAT_NAME_PREFIX) -> FLATTENED_REF;
112                 default -> OTHER;
113             };
114         }
115     }
116     private static ExternalizedTypeElement nameToType(String name) {
117         return ExternalizedTypeElement.of(name);
118     }
119     private static <T> T select(ExternalizedTypeElement tree, int index, Function<ExternalizedTypeElement, T> valueFunc) {
120         if (index >= tree.arguments().size()) {
121             throw new UnsupportedOperationException();
122         }
123         return valueFunc.apply(tree.arguments().get(index));
124     }
125     private static <T> List<T> selectFrom(ExternalizedTypeElement tree, int startIncl, Function<ExternalizedTypeElement, T> valueFunc) {
126         if (startIncl >= tree.arguments().size()) {
127             return List.of();
128         }
129         return IntStream.range(startIncl, tree.arguments().size())
130                 .mapToObj(i -> valueFunc.apply(tree.arguments().get(i)))
131                 .toList();
132     }
133 
134     private static String typeToName(ExternalizedTypeElement tree) {
135         if (!tree.arguments().isEmpty()) {
136             throw new UnsupportedOperationException();
137         }
138         return tree.identifier();
139     }
140 
141     /**
142      * {@return a flat string modelling the provided inflated Java reference form}.
143      * @param tree the inflated Java type form
144      */
145     public static String toExternalRefString(ExternalizedTypeElement tree) {
146         return switch (tree.identifier()) {
147             case JAVA_REF_FIELD_NAME -> {
148                 String owner = select(tree, 0, JavaTypeUtils::toExternalTypeString);
149                 String fieldName = select(tree, 1, JavaTypeUtils::typeToName);
150                 String fieldType = select(tree, 2, JavaTypeUtils::toExternalTypeString);
151                 yield String.format("%s::%s:%s", owner, fieldName, fieldType);
152             }
153             case JAVA_REF_METHOD_NAME -> {
154                 String owner = select(tree, 0, JavaTypeUtils::toExternalTypeString);
155                 ExternalizedTypeElement nameAndArgs = select(tree, 1, Function.identity());
156                 String methodName = nameAndArgs.identifier();
157                 List<String> paramTypes = selectFrom(nameAndArgs, 0, JavaTypeUtils::toExternalTypeString);
158                 String restype = select(tree, 2, JavaTypeUtils::toExternalTypeString);
159                 yield String.format("%s::%s(%s):%s", owner, methodName, String.join(", ", paramTypes), restype);
160             }
161             case JAVA_REF_CONSTRUCTOR_NAME -> {
162                 String owner = select(tree, 0, JavaTypeUtils::toExternalTypeString);
163                 ExternalizedTypeElement nameAndArgs = select(tree, 1, Function.identity());
164                 List<String> paramTypes = selectFrom(nameAndArgs, 0, JavaTypeUtils::toExternalTypeString);
165                 yield String.format("%s::(%s)", owner, String.join(", ", paramTypes));
166             }
167             case JAVA_REF_RECORD_NAME -> {
168                 String owner = select(tree, 0, JavaTypeUtils::toExternalTypeString);
169                 List<String> components = selectFrom(tree, 1, Function.identity()).stream()
170                         .map(t -> {
171                             String componentName = t.identifier();
172                             String componentType = select(t, 0, JavaTypeUtils::toExternalTypeString);
173                             return String.format("%s %s", componentType, componentName);
174                         }).toList();
175                 yield String.format("(%s)%s", String.join(", ", components), owner);
176             }
177             default ->  throw new UnsupportedOperationException();
178         };
179     }
180 
181     private static boolean isSameType(ExternalizedTypeElement tree, TypeElement typeElement) {
182         return tree.equals(typeElement.externalize());
183     }
184 
185     /**
186      * {@return a flat string modelling the provided inflated Java type form}.
187      * @param tree the inflated Java type form
188      */
189     public static String toExternalTypeString(ExternalizedTypeElement tree) {
190         return switch (tree.identifier()) {
191             case JAVA_TYPE_CLASS_NAME -> {
192                 String className = select(tree, 0, JavaTypeUtils::typeToName);
193                 ExternalizedTypeElement enclosing = select(tree, 1, Function.identity());
194                 String typeargs = tree.arguments().size() == 2 ?
195                         "" :
196                         selectFrom(tree, 2, JavaTypeUtils::toExternalTypeString).stream()
197                                 .collect(Collectors.joining(", ", "<", ">"));
198                 if (isSameType(enclosing, JavaType.VOID)) {
199                     yield String.format("%s%s", className, typeargs);
200                 } else {
201                     String enclosingString = toExternalTypeString(enclosing);
202                     yield String.format("%s::%s%s", enclosingString, className, typeargs);
203                 }
204             }
205             case JAVA_TYPE_ARRAY_NAME -> {
206                 String componentType = select(tree, 0, JavaTypeUtils::toExternalTypeString);
207                 yield String.format("%s[]", componentType);
208             }
209             case JAVA_TYPE_WILDCARD_NAME -> {
210                 WildcardType.BoundKind boundKind = select(tree, 0, t -> WildcardType.BoundKind.valueOf(typeToName(t)));
211                 ExternalizedTypeElement bound = select(tree, 1, Function.identity());
212                 yield boundKind == WildcardType.BoundKind.EXTENDS && isSameType(bound, JavaType.J_L_OBJECT) ?
213                         "?" :
214                         String.format("? %s %s", boundKind.name().toLowerCase(), toExternalTypeString(bound));
215             }
216             case JAVA_TYPE_VAR_NAME -> {
217                 String tvarName = select(tree, 0, JavaTypeUtils::typeToName);
218                 String owner = select(tree, 1, t ->
219                         switch (Kind.of(t)) {
220                             case INFLATED_REF -> "&" + toExternalRefString(t);
221                             case INFLATED_TYPE -> toExternalTypeString(t);
222                             default ->  throw new UnsupportedOperationException();
223                         });
224                 ExternalizedTypeElement bound = select(tree, 2, Function.identity());
225                 yield isSameType(bound, JavaType.J_L_OBJECT) ?
226                         String.format("%s::<%s>", owner, tvarName) :
227                         String.format("%s::<%s extends %s>", owner, tvarName, toExternalTypeString(bound));
228             }
229             case JAVA_TYPE_PRIMITIVE_NAME -> select(tree, 0, JavaTypeUtils::typeToName);
230             default -> throw  new UnsupportedOperationException();
231         };
232     }
233 
234     /**
235      * {@return the flat Java form corresponding to the provided inflated Java form}
236      * @param tree the inflated Java form
237      */
238     public static ExternalizedTypeElement flatten(ExternalizedTypeElement tree) {
239         return switch (Kind.of(tree)) {
240             case INFLATED_TYPE -> nameToType(String.format("%s\"%s\"", JAVA_TYPE_FLAT_NAME_PREFIX, toExternalTypeString(tree)));
241             case INFLATED_REF -> nameToType(String.format("%s\"%s\"", JAVA_REF_FLAT_NAME_PREFIX, toExternalRefString(tree)));
242             default -> ExternalizedTypeElement.of(tree.identifier(), tree.arguments().stream().map(JavaTypeUtils::flatten).toList());
243         };
244     }
245 
246 }
247     /**
248      * The attribute name associated with the location attribute.
249      */
250     static final String ATTRIBUTE_LOCATION = "loc";
251 
252     static final class GlobalValueBlockNaming implements Function<CodeItem, String> {
253         final Map<CodeItem, String> gn;
254         int valueOrdinal = 0;
255 
256         GlobalValueBlockNaming() {
257             this.gn = new HashMap<>();
258         }
259 
260         private String name(Block b) {
261             Block p = b.ancestorBlock();
262             return (p == null ? "block_" : name(p) + "_") + b.index();
263         }
264 
265         @Override
266         public String apply(CodeItem codeItem) {
267             return switch (codeItem) {
268                 case Block block -> gn.computeIfAbsent(block, _b -> name(block));
269                 case Value value -> gn.computeIfAbsent(value, _v -> String.valueOf(valueOrdinal++));
270                 default -> throw new IllegalStateException("Unexpected code item: " + codeItem);
271             };
272         }
273     }
274 
275     static final class AttributeMapper {
276         static String toString(Object value) {
277             if (value == ExternalizedOp.NULL_ATTRIBUTE_VALUE) {
278                 return "null";
279             }
280 
281             StringBuilder sb = new StringBuilder();
282             toString(value, sb);
283             return sb.toString();
284         }
285 
286         static void toString(Object o, StringBuilder sb) {
287             if (o.getClass().isArray()) {
288                 // note, while we can't parse back the array representation, this might be useful
289                 // for non-externalizable ops that want better string representation of array attribute values (e.g. ONNX)
290                 arrayToString(o, sb);
291             } else {
292                 switch (o) {
293                     case Integer i -> sb.append(i);
294                     case Long l -> sb.append(l).append('L');
295                     case Float f -> sb.append(f).append('f');
296                     case Double d -> sb.append(d).append('d');
297                     case Character c -> sb.append('\'').append(c).append('\'');
298                     case Boolean b -> sb.append(b);
299                     case TypeElement te -> sb.append(JavaTypeUtils.flatten(te.externalize()).toString());
300                     default -> {  // fallback to a string
301                         sb.append('"');
302                         quote(o.toString(), sb);
303                         sb.append('"');
304                     }
305                 }
306             }
307         }
308 
309         static void arrayToString(Object a, StringBuilder sb) {
310             boolean first = true;
311             sb.append("[");
312             for (int i = 0; i < Array.getLength(a); i++) {
313                 if (!first) {
314                     sb.append(", ");
315                 }
316                 toString(Array.get(a, i), sb);
317                 first = false;
318             }
319             sb.append("]");
320         }
321     }
322 
323     static void quote(String s, StringBuilder sb) {
324         for (int i = 0; i < s.length(); i++) {
325             sb.append(quote(s.charAt(i)));
326         }
327     }
328 
329     /**
330      * Escapes a character if it has an escape sequence or is
331      * non-printable ASCII.  Leaves non-ASCII characters alone.
332      */
333     // Copied from com.sun.tools.javac.util.Convert
334     static String quote(char ch) {
335         return switch (ch) {
336             case '\b' -> "\\b";
337             case '\f' -> "\\f";
338             case '\n' -> "\\n";
339             case '\r' -> "\\r";
340             case '\t' -> "\\t";
341             case '\'' -> "\\'";
342             case '\"' -> "\\\"";
343             case '\\' -> "\\\\";
344             default -> (isPrintableAscii(ch))
345                     ? String.valueOf(ch)
346                     : String.format("\\u%04x", (int) ch);
347         };
348     }
349 
350     /**
351      * Is a character printable ASCII?
352      */
353     static boolean isPrintableAscii(char ch) {
354         return ch >= ' ' && ch <= '~';
355     }
356 
357     static final class IndentWriter {
358         static final int INDENT = 2;
359         private final Writer w;
360         private int indent;
361         private boolean writeIndent = true;
362 
363         IndentWriter(Writer w) {
364             this(w, 0);
365         }
366 
367         IndentWriter(Writer w, int indent) {
368             this.w = w;
369             this.indent = indent;
370         }
371 
372 
373         public void write(String s)  {
374             try {
375                 if (writeIndent) {
376                     w.write(" ".repeat(indent));
377                     writeIndent = false;
378                 }
379                 w.write(s);
380             } catch (IOException e) {
381                 throw new UncheckedIOException(e);
382             }
383 
384         }
385 
386         public void nl(){
387             write("\n");
388             writeIndent=true;
389         }
390 
391         public void symbol(String symbol){
392            write(symbol);
393         }
394 
395         void space(){
396             write(" ");
397         }
398 
399         void in() {
400             indent += INDENT;
401         }
402         void out() {
403             indent -= INDENT;
404         }
405     }
406 
407     /**
408      * Computes global names for blocks and values in a code model.
409      * <p>
410      * The code model is traversed in the same order as if the model
411      * was written. Therefore, the names in the returned map will the
412      * same as the names that are written. This can be useful for debugging
413      * and testing.
414      *
415      * @param root the code model
416      * @return the map of computed names, modifiable
417      */
418     public static Function<CodeItem, String> computeGlobalNames(Op root) {
419         OpCodeBuilder w = new OpCodeBuilder(Writer.nullWriter());
420         w.writeOp(root);
421         return w.namer();
422     }
423 
424     /**
425      * Writes a code model (an operation) to the output stream, using the UTF-8 character set.
426      *
427      * @param out the output stream
428      * @param op the code model
429      */
430     public static void writeTo(OutputStream out, Op op, Option... options) {
431         writeTo(new OutputStreamWriter(out, StandardCharsets.UTF_8), op, options);
432     }
433 
434     /**
435      * Writes a code model (an operation) to the character stream.
436      * <p>
437      * The character stream will be flushed after the model is writen.
438      *
439      * @param w the character stream
440      * @param op the code model
441      * @param options the writer options
442      */
443     public static void writeTo(Writer w, Op op, Option... options) {
444         OpCodeBuilder ow = new OpCodeBuilder(w, options);
445         ow.writeOp(op);
446         try {
447             // @@@ Is this needed?
448             w.flush();
449         } catch (IOException e) {
450             throw new UncheckedIOException(e);
451         }
452     }
453 
454     /**
455      * Writes a code model (an operation) to a string.
456      *
457      * @param op the code model
458      * @param options the writer options
459      */
460     public static String toText(Op op, OpCodeBuilder.Option... options) {
461         StringWriter w = new StringWriter();
462         writeTo(w, op, options);
463         return w.toString();
464     }
465 
466     /**
467      * An option that affects the writing operations.
468      */
469     public sealed interface Option {
470     }
471 
472     /**
473      * An option describing the function to use for naming code items.
474      */
475     public sealed interface CodeItemNamerOption extends Option
476             permits NamerOptionImpl {
477 
478         static CodeItemNamerOption of(Function<CodeItem, String> named) {
479             return new NamerOptionImpl(named);
480         }
481 
482         static CodeItemNamerOption defaultValue() {
483             return of(new GlobalValueBlockNaming());
484         }
485 
486         Function<CodeItem, String> namer();
487     }
488     private record NamerOptionImpl(Function<CodeItem, String> namer) implements CodeItemNamerOption {
489     }
490 
491     /**
492      * An option describing whether location information should be written or dropped.
493      */
494     public enum LocationOption implements Option {
495         /** Writes location */
496         WRITE_LOCATION,
497         /** Drops location */
498         DROP_LOCATION;
499 
500         public static LocationOption defaultValue() {
501             return WRITE_LOCATION;
502         }
503     }
504 
505     /**
506      * An option describing whether an operation's descendant code elements should be written or dropped.
507      */
508     public enum OpDescendantsOption implements Option {
509         /** Writes descendants of an operation, if any */
510         WRITE_DESCENDANTS,
511         /** Drops descendants of an operation, if any */
512         DROP_DESCENDANTS;
513 
514         public static OpDescendantsOption defaultValue() {
515             return WRITE_DESCENDANTS;
516         }
517     }
518 
519     /**
520      * An option describing whether an operation's result be written or dropped if its type is void.
521      */
522     public enum VoidOpResultOption implements Option {
523         /** Writes void operation result */
524         WRITE_VOID,
525         /** Drops void operation result */
526         DROP_VOID;
527 
528         public static VoidOpResultOption defaultValue() {
529             return DROP_VOID;
530         }
531     }
532 
533     final Function<CodeItem, String> namer;
534     final IndentWriter w;
535     final boolean dropLocation;
536     final boolean dropOpDescendants;
537     final boolean writeVoidOpResult;
538 
539     /**
540      * Creates a writer of code models (operations) to their textual form.
541      *
542      * @param w the character stream writer to write the textual form.
543      */
544     public OpCodeBuilder(Writer w) {
545         this.w = new IndentWriter(w);
546         this.namer = new GlobalValueBlockNaming();
547         this.dropLocation = false;
548         this.dropOpDescendants = false;
549         this.writeVoidOpResult = false;
550     }
551 
552     /**
553      * Creates a writer of code models (operations) to their textual form.
554      *
555      * @param w the character stream writer to write the textual form.
556      * @param options the writer options
557      */
558     public OpCodeBuilder(Writer w, Option... options) {
559         Function<CodeItem, String> namer = null;
560         boolean dropLocation = false;
561         boolean dropOpDescendants = false;
562         boolean writeVoidOpResult = false;
563         for (Option option : options) {
564             switch (option) {
565                 case CodeItemNamerOption namerOption -> {
566                     namer = namerOption.namer();
567                 }
568                 case LocationOption locationOption -> {
569                     dropLocation = locationOption ==
570                             LocationOption.DROP_LOCATION;
571                 }
572                 case OpDescendantsOption opDescendantsOption -> {
573                     dropOpDescendants = opDescendantsOption ==
574                             OpDescendantsOption.DROP_DESCENDANTS;
575                 }
576                 case VoidOpResultOption voidOpResultOption -> {
577                     writeVoidOpResult = voidOpResultOption == VoidOpResultOption.WRITE_VOID;
578                 }
579             }
580         }
581 
582         this.w = new IndentWriter(w);
583         this.namer = (namer == null) ? new GlobalValueBlockNaming() : namer;
584         this.dropLocation = dropLocation;
585         this.dropOpDescendants = dropOpDescendants;
586         this.writeVoidOpResult = writeVoidOpResult;
587     }
588 
589     /**
590      * {@return the function that names blocks and values.}
591      */
592     public Function<CodeItem, String> namer() {
593         return namer;
594     }
595 
596     /**
597      * Writes a code model, an operation, to the character stream.
598      *
599      * @param op the code model
600      */
601     public OpCodeBuilder writeOp(Op op) {
602         if (op.parent() != null) {
603             Op.Result opr = op.result();
604             if (writeVoidOpResult || !opr.type().equals(JavaType.VOID)) {
605                 writeValueDeclaration(opr).space().equal().space();
606             }
607         }
608         write(op.externalizeOpName());
609 
610         if (!op.operands().isEmpty()) {
611             space().writeSpaceSeparatedList(op.operands(), this::writeValueUse);
612         }
613 
614         if (!op.successors().isEmpty()) {
615             space().writeSpaceSeparatedList(op.successors(), this::writeSuccessor);
616         }
617 
618         if (!dropLocation) {
619             Location location = op.location();
620             if (location != null) {
621                 space().writeAttribute(ATTRIBUTE_LOCATION, op.location());
622             }
623         }
624         Map<String, Object> attributes = op.externalize();
625         if (!attributes.isEmpty()) {
626             space().writeSpaceSeparatedList(attributes.entrySet(), e -> writeAttribute(e.getKey(), e.getValue()));
627         }
628 
629         if (!dropOpDescendants && !op.bodies().isEmpty()) {
630             int nBodies = op.bodies().size();
631             if (nBodies == 1) {
632                 space();
633             } else {
634                 nl().in().in();
635             }
636             boolean first = true;
637             for (Body body : op.bodies()) {
638                 if (!first) {
639                     nl();
640                 }
641                 writeBody(body);
642                 first = false;
643             }
644             if (nBodies > 1) {
645                 out().out();
646             }
647         }
648         semicolon();
649         return this;
650     }
651 
652     OpCodeBuilder writeSuccessor(Block.Reference successor) {
653         writeBlockName(successor.targetBlock());
654         if (!successor.arguments().isEmpty()) {
655             oparen().nl().in().writeCommaSeparatedList(successor.arguments(), this::writeValueUse).out().nl().cparen();
656         }
657         return this;
658     }
659 
660     OpCodeBuilder writeAttribute(String name, Object value) {
661         at();
662         if (!name.isEmpty()) {
663             write(name);
664             equal();
665         }
666         write(AttributeMapper.toString(value));
667         return this;
668     }
669 
670     OpCodeBuilder writeBody(Body body) {
671         Block eb = body.entryBlock();
672         oparen();
673         if (!eb.parameters().isEmpty()) {
674             nl().in().writeCommaSeparatedList(eb.parameters(), this::writeValueDeclaration).out().nl();
675         }
676         cparen();
677         writeType(body.bodyType().returnType());
678         space().arrow().space();
679         obrace().nl().in();
680         for (Block b : body.blocks()) {
681             if (!b.isEntryBlock()) {
682                 nl();
683             }
684             writeBlock(b);
685         }
686         out().cbrace();
687         return this;
688     }
689 
690     OpCodeBuilder writeBlock(Block block) {
691         if (!block.isEntryBlock()) {
692             writeBlockName(block);
693             if (!block.parameters().isEmpty()) {
694                 oparen().nl().in().writeCommaSeparatedList(block.parameters(), this::writeValueDeclaration).out().nl().cparen();
695             }
696             colon().nl();
697         }
698         in();
699         for (Op op : block.ops()) {
700             writeOp(op).nl();
701         }
702         out();
703         return this;
704     }
705 
706     OpCodeBuilder writeBlockName(Block b) {
707        return hat().write(namer.apply(b));
708     }
709 
710     OpCodeBuilder ssaid(Value v){
711        return percent().write(namer.apply(v));
712     }
713 
714     OpCodeBuilder writeValueUse(Value v) {
715        return  ssaid(v);
716     }
717 
718     OpCodeBuilder writeValueDeclaration(Value v) {
719         return ssaid(v).space().colon().space().writeType(v.type());
720     }
721 
722     <T> OpCodeBuilder writeSpaceSeparatedList(Iterable<T> l, Consumer<T> c) {
723         return writeSeparatedList(" ", l, c);
724     }
725 
726     <T> OpCodeBuilder writeCommaSeparatedList(Iterable<T> l, Consumer<T> c) {
727         return writeSeparatedNlList(", ", l, c);
728     }
729 
730     <T> OpCodeBuilder writeSeparatedList(String separator, Iterable<T> l, Consumer<T> c) {
731         boolean first = true;
732         for (T t : l) {
733             if (!first) {
734                 write(separator);
735             }
736             c.accept(t);
737             first = false;
738         }
739         return this;
740     }
741     <T> OpCodeBuilder writeSeparatedNlList(String separator, Iterable<T> l, Consumer<T> c) {
742         boolean first = true;
743         for (T t : l) {
744             if (!first) {
745                 write(separator);
746                 nl();
747             }
748             c.accept(t);
749             first = false;
750         }
751         return this;
752     }
753     OpCodeBuilder writeType(TypeElement te) {
754         write(JavaTypeUtils.flatten(te.externalize()).toString());
755         return this;
756     }
757 
758     OpCodeBuilder write(String s) {
759         w.write(s);
760         return this;
761     }
762     OpCodeBuilder nl() {
763           w.nl();
764         return this;
765     }
766 
767     OpCodeBuilder semicolon(){
768         w.symbol(";");
769         return this;
770     }
771 
772     OpCodeBuilder space(){
773         w.space();
774         return this;
775     }
776     OpCodeBuilder colon(){
777         w.symbol(":");
778         return this;
779     }
780     OpCodeBuilder oparen(){
781         w.symbol("(");
782         return this;
783     }
784     OpCodeBuilder obrace(){
785         w.symbol("{");
786         return this;
787     }
788     OpCodeBuilder cparen(){
789         w.symbol(")");
790         return this;
791     }
792     OpCodeBuilder cbrace(){
793         w.symbol("}");
794         return this;
795     }
796     OpCodeBuilder equal(){
797         w.symbol("=");
798         return this;
799     }
800     OpCodeBuilder in(){
801         w.in();
802         return this;
803     }
804     OpCodeBuilder out(){
805         w.out();
806         return this;
807     }
808     OpCodeBuilder arrow(){
809         w.symbol("->");
810         return this;
811     }
812     OpCodeBuilder at(){
813         w.symbol("@");
814         return this;
815     }
816     OpCodeBuilder hat(){
817         w.symbol("^");
818         return this;
819     }
820     OpCodeBuilder percent(){
821         w.symbol("%");
822         return this;
823     }
824 }