1 /* 2 * Copyright (c) 2018, 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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package com.sun.tools.jextract.parser; 24 25 import java.nio.file.Path; 26 import java.nio.file.Paths; 27 import java.io.PrintWriter; 28 import java.util.Arrays; 29 import java.util.ArrayList; 30 import java.util.Collection; 31 import java.util.Collections; 32 import java.util.LinkedHashMap; 33 import java.util.List; 34 import java.util.Map; 35 import java.util.Optional; 36 import java.util.logging.Logger; 37 import java.util.stream.Collectors; 38 import jdk.internal.clang.Cursor; 39 import jdk.internal.clang.CursorKind; 40 import jdk.internal.clang.Diagnostic; 41 import jdk.internal.clang.Index; 42 import jdk.internal.clang.LibClang; 43 import jdk.internal.clang.SourceLocation; 44 import jdk.internal.clang.SourceRange; 45 import jdk.internal.clang.TranslationUnit; 46 import com.sun.tools.jextract.tree.HeaderTree; 47 import com.sun.tools.jextract.tree.MacroTree; 48 import com.sun.tools.jextract.tree.SimpleTreeVisitor; 49 import com.sun.tools.jextract.tree.Tree; 50 import com.sun.tools.jextract.tree.TreeMaker; 51 import com.sun.tools.jextract.tree.TreePrinter; 52 53 public class Parser { 54 private final PrintWriter out; 55 private final PrintWriter err; 56 private final TreeMaker treeMaker; 57 private final boolean supportMacros; 58 private final Logger logger = Logger.getLogger(getClass().getPackage().getName()); 59 60 public Parser(PrintWriter out, PrintWriter err, boolean supportMacros) { 61 this.out = out; 62 this.err = err; 63 this.treeMaker = new TreeMaker(); 64 this.supportMacros = supportMacros; 65 } 66 67 public Parser(boolean supportMacros) { 68 this(new PrintWriter(System.out, true), 69 new PrintWriter(System.err, true), supportMacros); 70 } 71 72 public List<HeaderTree> parse(Collection<Path> paths, Collection<String> args) { 73 final List<HeaderTree> headers = new ArrayList<>(); 74 final Index index = LibClang.createIndex(); 75 for (Path path : paths) { 76 logger.info(() -> { 77 StringBuilder sb = new StringBuilder( 78 "Parsing header file " + path + " with following args:\n"); 79 int i = 0; 80 for (String arg : args) { 81 sb.append("arg["); 82 sb.append(i++); 83 sb.append("] = "); 84 sb.append(arg); 85 sb.append("\n"); 86 } 87 return sb.toString(); 88 }); 89 90 Cursor tuCursor = index.parse(path.toString(), 91 d -> { 92 err.println(d); 93 if (d.severity() > Diagnostic.CXDiagnostic_Warning) { 94 throw new RuntimeException(d.toString()); 95 } 96 }, 97 supportMacros, args.toArray(new String[0])); 98 99 MacroParser macroParser = new MacroParser(); 100 List<Tree> decls = new ArrayList<>(); 101 tuCursor.children(). 102 peek(c -> logger.finest( 103 () -> "Cursor: " + c.spelling() + "@" + c.USR() + "?" + c.isDeclaration())). 104 forEach(c -> { 105 SourceLocation loc = c.getSourceLocation(); 106 if (loc == null) { 107 logger.info(() -> "Ignore Cursor " + c.spelling() + "@" + c.USR() + " has no SourceLocation"); 108 return; 109 } 110 111 SourceLocation.Location src = loc.getFileLocation(); 112 if (src == null) { 113 logger.info(() -> "Cursor " + c.spelling() + "@" + c.USR() + " has no FileLocation"); 114 return; 115 } 116 117 logger.fine(() -> "Do cursor: " + c.spelling() + "@" + c.USR()); 118 119 if (c.isDeclaration()) { 120 if (c.kind() == CursorKind.UnexposedDecl || 121 c.kind() == CursorKind.Namespace) { 122 c.children().forEach(cu -> decls.add(treeMaker.createTree(cu))); 123 } else { 124 decls.add(treeMaker.createTree(c)); 125 } 126 } else if (supportMacros && isMacro(c) && src.path() != null) { 127 handleMacro(macroParser, c); 128 } 129 }); 130 131 decls.addAll(macros(macroParser)); 132 headers.add(treeMaker.createHeader(tuCursor, path, decls)); 133 } 134 135 return Collections.unmodifiableList(headers); 136 } 137 138 private List<MacroTree> macros(MacroParser macroParser) { 139 return macroParser.macros().stream() 140 .map(m -> treeMaker.createMacro(m.cursor(), macroValue(m))) 141 .collect(Collectors.toList()); 142 } 143 144 private Optional<Object> macroValue(MacroParser.Macro m) { 145 try { 146 return Optional.ofNullable(m.value()); 147 } catch (Exception e) { 148 return Optional.empty(); 149 } 150 } 151 152 private void handleMacro(MacroParser macroParser, Cursor cursor) { 153 String macroName = cursor.spelling(); 154 if (cursor.isMacroFunctionLike()) { 155 logger.fine(() -> "Skipping function-like macro " + macroName); 156 return; 157 } 158 159 if (macroParser.isDefined(macroName)) { 160 logger.fine(() -> "Macro " + macroName + " already handled"); 161 return; 162 } 163 164 logger.fine(() -> "Defining macro " + macroName); 165 166 TranslationUnit tu = cursor.getTranslationUnit(); 167 SourceRange range = cursor.getExtent(); 168 String[] tokens = tu.tokens(range); 169 170 macroParser.parse(cursor, tokens); 171 } 172 173 private boolean isMacro(Cursor c) { 174 return c.isPreprocessing() && c.kind() == CursorKind.MacroDefinition; 175 } 176 177 public static void main(String[] args) { 178 if (args.length == 0) { 179 System.err.println("Expected a header file"); 180 return; 181 } 182 183 Parser p = new Parser(true); 184 List<Path> paths = Arrays.stream(args).map(Paths::get).collect(Collectors.toList()); 185 Path builtinInc = Paths.get(System.getProperty("java.home"), "conf", "jextract"); 186 List<String> clangArgs = List.of("-I" + builtinInc); 187 List<HeaderTree> headers = p.parse(paths, clangArgs); 188 TreePrinter printer = new TreePrinter(); 189 for (HeaderTree ht : headers) { 190 ht.accept(printer, null); 191 } 192 } 193 }