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 jdk.incubator.code.tools.renderer; 27 28 import java.io.IOException; 29 import java.io.UncheckedIOException; 30 import java.io.Writer; 31 import java.util.Map; 32 33 public class TextRenderer<T extends TextRenderer<T>> { 34 35 public interface NestedRendererSAM<T> { 36 T build(T nb); 37 } 38 39 public enum TokenType { 40 WHITESPACE, OP, TYPE, CONTROL, LITERAL, COMMENT, KEYWORD, IDENTIFIER, LABELTARGET, NONE 41 } 42 43 public static class TokenColorMap { 44 45 46 private final Map<TokenType, TerminalColors.Colorizer> map; 47 48 public TokenColorMap(Map<TokenType, TerminalColors.Colorizer> map) { 49 this.map = map; 50 } 51 52 public TokenColorMap() { 53 this(Map.of( 54 TokenType.NONE, TerminalColors.Color.WHITE, 55 TokenType.IDENTIFIER, TerminalColors.Color.YELLOW, 56 TokenType.LABELTARGET, TerminalColors.Color.BLUE, 57 TokenType.TYPE, TerminalColors.Color.WHITE, 58 TokenType.COMMENT, TerminalColors.Color.GREEN, 59 TokenType.KEYWORD, TerminalColors.Color.ORANGE, 60 TokenType.CONTROL, TerminalColors.Color.GREY, 61 TokenType.LITERAL, TerminalColors.Color.GREEN, 62 TokenType.OP, TerminalColors.Color.WHITE, 63 TokenType.WHITESPACE, TerminalColors.Color.WHITE)); 64 } 65 66 public String colorize(TokenType tokenType, String string) { 67 if (map.containsKey(tokenType)) { 68 return map.get(tokenType).colorize(string); 69 } else { 70 return string; 71 } 72 } 73 } 74 75 public static class State { 76 public Writer writer; 77 public int indent; 78 public TokenColorMap tokenColorMap = null; 79 public boolean isFirst = false; 80 81 public boolean newLined = false; 82 83 State() { 84 this.writer = null; 85 this.indent = 0; 86 this.tokenColorMap = null; 87 this.isFirst = false; 88 this.newLined = true; 89 } 90 } 91 92 public T writer(Writer writer) { 93 this.state.writer = writer; 94 return self(); 95 } 96 97 public T colorize(TokenColorMap tokenColorMap) { 98 this.state.tokenColorMap = tokenColorMap; 99 return self(); 100 } 101 102 public T colorize() { 103 return colorize(new TokenColorMap()); 104 } 105 106 public State state; 107 108 protected TextRenderer() { 109 this.state = new State(); 110 } 111 112 protected TextRenderer(TextRenderer<?> renderer) { 113 this.state = renderer.state; 114 } 115 116 @SuppressWarnings("unchecked") 117 public T self() { 118 return (T) this; 119 } 120 121 122 public boolean first() { 123 var was = state.isFirst; 124 state.isFirst = false; 125 return was; 126 } 127 128 public T startList() { 129 state.isFirst = true; 130 return self(); 131 } 132 133 public T append(String text) { 134 try { 135 // While we do expect appends text to be simple tokens. We can handle newlines. 136 var lines = text.split("\n"); 137 for (int i = 0; i < lines.length - 1; i++) { 138 state.writer.append(" ".repeat(state.indent) + lines[i] + "\n"); 139 state.newLined = true; 140 } 141 if (state.newLined) { 142 state.writer.append(" ".repeat(state.indent)); 143 } 144 state.writer.append(lines[lines.length - 1]); 145 state.newLined = false; 146 return self(); 147 } catch (IOException ioe) { 148 throw new UncheckedIOException(ioe); 149 } 150 } 151 152 public T identifier(String ident) { 153 if (state.tokenColorMap != null) { 154 return append(state.tokenColorMap.colorize(TokenType.IDENTIFIER, ident)); 155 } else { 156 return append(ident); 157 } 158 } 159 160 public T type(String typeName) { 161 if (state.tokenColorMap != null) { 162 return append(state.tokenColorMap.colorize(TokenType.TYPE, typeName)); 163 } else { 164 return append(typeName); 165 } 166 } 167 168 public T keyword(String keyword) { 169 if (state.tokenColorMap != null) { 170 return append(state.tokenColorMap.colorize(TokenType.KEYWORD, keyword)); 171 } else { 172 return append(keyword); 173 } 174 } 175 176 public T literal(String literal) { 177 if (state.tokenColorMap != null) { 178 return append(state.tokenColorMap.colorize(TokenType.LITERAL, literal)); 179 } else { 180 return append(literal); 181 } 182 } 183 184 public T ws(String whitespace) { 185 if (state.tokenColorMap != null) { 186 return append(state.tokenColorMap.colorize(TokenType.WHITESPACE, whitespace)); 187 } else { 188 return append(whitespace); 189 } 190 } 191 192 public T op(String op) { 193 if (state.tokenColorMap != null) { 194 return append(state.tokenColorMap.colorize(TokenType.OP, op)); 195 } else { 196 return append(op); 197 } 198 } 199 200 public T control(String control) { 201 if (state.tokenColorMap != null) { 202 return append(state.tokenColorMap.colorize(TokenType.CONTROL, control)); 203 } else { 204 return append(control); 205 } 206 } 207 208 public T labelTarget(String labelTarget) { 209 if (state.tokenColorMap != null) { 210 return append(state.tokenColorMap.colorize(TokenType.LABELTARGET, labelTarget)); 211 } else { 212 return append(labelTarget); 213 } 214 } 215 216 public T comment(String comment) { 217 if (state.tokenColorMap != null) { 218 return append(state.tokenColorMap.colorize(TokenType.COMMENT, comment)); 219 } else { 220 return append(comment); 221 } 222 } 223 224 public T strLiteral(String s) { 225 return oquot().literal(s).cquot(); 226 } 227 228 public T oquot() { 229 return literal("\""); 230 } 231 232 public T cquot() { 233 return literal("\""); 234 } 235 236 public T decLiteral(int i) { 237 return literal(String.format("%d", i)); 238 } 239 240 public T hexLiteral(int i) { 241 return literal(String.format("%x", i)); 242 } 243 244 public T in() { 245 state.indent += 2; 246 return self(); 247 } 248 249 public T out() { 250 state.indent -= 2; 251 return self(); 252 } 253 254 public T flush() { 255 try { 256 state.writer.flush(); 257 return self(); 258 } catch (IOException ioe) { 259 throw new RuntimeException(ioe); 260 } 261 } 262 263 public T nl() { 264 try { 265 // note we go directly to the underlying writer! 266 state.writer.append("\n"); 267 state.newLined = true; 268 return flush().self(); 269 } catch (IOException ioe) { 270 throw new RuntimeException(ioe); 271 } 272 } 273 274 public T space() { 275 return ws(" "); 276 } 277 278 public T nest(NestedRendererSAM<T> nb) { 279 return nb.build(self()); 280 } 281 282 public T open(String op) { 283 control(op); 284 return self(); 285 } 286 287 public T close(String op) { 288 control(op); 289 return self(); 290 } 291 } 292